1
0
mirror of https://github.com/ppy/osu.git synced 2025-03-28 09:37:23 +08:00

Merge branch 'master' into verify-breaks

This commit is contained in:
Bartłomiej Dach 2023-07-22 17:42:55 +02:00
commit d7b674bf2b
No known key found for this signature in database
74 changed files with 871 additions and 411 deletions

1
.gitignore vendored
View File

@ -339,6 +339,5 @@ inspectcode
# Fody (pulled in by Realm) - schema file # Fody (pulled in by Realm) - schema file
FodyWeavers.xsd FodyWeavers.xsd
**/FodyWeavers.xml
.idea/.idea.osu.Desktop/.idea/misc.xml .idea/.idea.osu.Desktop/.idea/misc.xml

View File

@ -8,13 +8,9 @@
<!-- NullabilityInfoContextSupport is disabled by default for Android --> <!-- NullabilityInfoContextSupport is disabled by default for Android -->
<NullabilityInfoContextSupport>true</NullabilityInfoContextSupport> <NullabilityInfoContextSupport>true</NullabilityInfoContextSupport>
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk> <EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
<AndroidManifestMerger>manifestmerger.jar</AndroidManifestMerger>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.716.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2023.720.0" />
</ItemGroup>
<ItemGroup>
<AndroidManifestOverlay Include="$(MSBuildThisFileDirectory)osu.Android\Properties\AndroidManifestOverlay.xml" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>
<!-- Fody does not handle Android build well, and warns when unchanged. <!-- Fody does not handle Android build well, and warns when unchanged.

View File

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="mailto" />
</intent>
</queries>
</manifest>

View File

@ -54,9 +54,6 @@ namespace osu.Desktop
client.OnReady += onReady; client.OnReady += onReady;
// safety measure for now, until we performance test / improve backoff for failed connections.
client.OnConnectionFailed += (_, _) => client.Deinitialize();
client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Code} {e.Message}", LoggingTarget.Network); client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Code} {e.Message}", LoggingTarget.Network);
config.BindWith(OsuSetting.DiscordRichPresence, privacyMode); config.BindWith(OsuSetting.DiscordRichPresence, privacyMode);

View File

@ -26,7 +26,7 @@
<PackageReference Include="Clowd.Squirrel" Version="2.9.42" /> <PackageReference Include="Clowd.Squirrel" Version="2.9.42" />
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" /> <PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
<PackageReference Include="System.IO.Packaging" Version="7.0.0" /> <PackageReference Include="System.IO.Packaging" Version="7.0.0" />
<PackageReference Include="DiscordRichPresence" Version="1.1.3.18" /> <PackageReference Include="DiscordRichPresence" Version="1.1.4.20" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Resources"> <ItemGroup Label="Resources">
<EmbeddedResource Include="lazer.ico" /> <EmbeddedResource Include="lazer.ico" />

View File

@ -0,0 +1,22 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Screens.Edit.Setup;
namespace osu.Game.Rulesets.Mania.Edit.Setup
{
public partial class ManiaDifficultySection : DifficultySection
{
[BackgroundDependencyLoader]
private void load()
{
CircleSizeSlider.Label = BeatmapsetsStrings.ShowStatsCsMania;
CircleSizeSlider.Description = "The number of columns in the beatmap";
if (CircleSizeSlider.Current is BindableNumber<float> circleSizeFloat)
circleSizeFloat.Precision = 1;
}
}
}

View File

@ -425,6 +425,8 @@ namespace osu.Game.Rulesets.Mania
} }
public override RulesetSetupSection CreateEditorSetupSection() => new ManiaSetupSection(); public override RulesetSetupSection CreateEditorSetupSection() => new ManiaSetupSection();
public override DifficultySection CreateEditorDifficultySection() => new ManiaDifficultySection();
} }
public enum PlayfieldType public enum PlayfieldType

View File

@ -62,12 +62,7 @@ namespace osu.Game.Rulesets.Osu.Edit
private void load() private void load()
{ {
// Give a bit of breathing room around the playfield content. // Give a bit of breathing room around the playfield content.
PlayfieldContentContainer.Padding = new MarginPadding PlayfieldContentContainer.Padding = new MarginPadding(10);
{
Vertical = 10,
Left = TOOLBOX_CONTRACTED_SIZE_LEFT + 10,
Right = TOOLBOX_CONTRACTED_SIZE_RIGHT + 10,
};
LayerBelowRuleset.AddRange(new Drawable[] LayerBelowRuleset.AddRange(new Drawable[]
{ {

View File

@ -22,7 +22,6 @@ namespace osu.Game.Tests.Visual.Editing
public partial class TestSceneBeatDivisorControl : OsuManualInputManagerTestScene public partial class TestSceneBeatDivisorControl : OsuManualInputManagerTestScene
{ {
private BeatDivisorControl beatDivisorControl = null!; private BeatDivisorControl beatDivisorControl = null!;
private BindableBeatDivisor bindableBeatDivisor = null!;
private SliderBar<int> tickSliderBar => beatDivisorControl.ChildrenOfType<SliderBar<int>>().Single(); private SliderBar<int> tickSliderBar => beatDivisorControl.ChildrenOfType<SliderBar<int>>().Single();
private Triangle tickMarkerHead => tickSliderBar.ChildrenOfType<Triangle>().Single(); private Triangle tickMarkerHead => tickSliderBar.ChildrenOfType<Triangle>().Single();
@ -30,13 +29,19 @@ namespace osu.Game.Tests.Visual.Editing
[Cached] [Cached]
private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Aquamarine); private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
[Cached]
private readonly BindableBeatDivisor bindableBeatDivisor = new BindableBeatDivisor(16);
[SetUp] [SetUp]
public void SetUp() => Schedule(() => public void SetUp() => Schedule(() =>
{ {
bindableBeatDivisor.ValidDivisors.SetDefault();
bindableBeatDivisor.SetDefault();
Child = new PopoverContainer Child = new PopoverContainer
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Child = beatDivisorControl = new BeatDivisorControl(bindableBeatDivisor = new BindableBeatDivisor(16)) Child = beatDivisorControl = new BeatDivisorControl
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,

View File

@ -1,26 +1,24 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System; using System;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Testing;
using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Osu.UI;
using osu.Game.Tests.Beatmaps;
using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Edit.Compose.Components;
using osu.Game.Tests.Beatmaps;
using osuTK; using osuTK;
using osuTK.Input; using osuTK.Input;
@ -82,7 +80,7 @@ namespace osu.Game.Tests.Visual.Editing
[Test] [Test]
public void TestNudgeSelection() public void TestNudgeSelection()
{ {
HitCircle[] addedObjects = null; HitCircle[] addedObjects = null!;
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[] AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[]
{ {
@ -104,7 +102,7 @@ namespace osu.Game.Tests.Visual.Editing
[Test] [Test]
public void TestRotateHotkeys() public void TestRotateHotkeys()
{ {
HitCircle[] addedObjects = null; HitCircle[] addedObjects = null!;
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[] AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[]
{ {
@ -136,7 +134,7 @@ namespace osu.Game.Tests.Visual.Editing
[Test] [Test]
public void TestGlobalFlipHotkeys() public void TestGlobalFlipHotkeys()
{ {
HitCircle addedObject = null; HitCircle addedObject = null!;
AddStep("add hitobjects", () => EditorBeatmap.Add(addedObject = new HitCircle { StartTime = 100 })); AddStep("add hitobjects", () => EditorBeatmap.Add(addedObject = new HitCircle { StartTime = 100 }));
@ -217,11 +215,173 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("2 hitobjects selected", () => EditorBeatmap.SelectedHitObjects.Count == 2 && !EditorBeatmap.SelectedHitObjects.Contains(addedObjects[1])); AddAssert("2 hitobjects selected", () => EditorBeatmap.SelectedHitObjects.Count == 2 && !EditorBeatmap.SelectedHitObjects.Contains(addedObjects[1]));
} }
[Test]
public void TestNearestSelection()
{
var firstObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 0 };
var secondObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 600 };
AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[] { firstObject, secondObject }));
moveMouseToObject(() => firstObject);
AddStep("seek near first", () => EditorClock.Seek(100));
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("first selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(firstObject));
AddStep("deselect", () => EditorBeatmap.SelectedHitObjects.Clear());
AddStep("seek near second", () => EditorClock.Seek(500));
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("second selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(secondObject));
AddStep("deselect", () => EditorBeatmap.SelectedHitObjects.Clear());
AddStep("seek halfway", () => EditorClock.Seek(300));
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("first selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(firstObject));
}
[Test]
public void TestNearestSelectionWithEndTime()
{
var firstObject = new Slider
{
Position = new Vector2(256, 192),
StartTime = 0,
Path = new SliderPath(new[]
{
new PathControlPoint(),
new PathControlPoint(new Vector2(50, 0)),
})
};
var secondObject = new HitCircle
{
Position = new Vector2(256, 192),
StartTime = 600
};
AddStep("add hitobjects", () => EditorBeatmap.AddRange(new HitObject[] { firstObject, secondObject }));
moveMouseToObject(() => firstObject);
AddStep("seek near first", () => EditorClock.Seek(100));
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("first selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(firstObject));
AddStep("deselect", () => EditorBeatmap.SelectedHitObjects.Clear());
AddStep("seek near second", () => EditorClock.Seek(500));
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("second selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(secondObject));
AddStep("deselect", () => EditorBeatmap.SelectedHitObjects.Clear());
AddStep("seek roughly halfway", () => EditorClock.Seek(350));
AddStep("left click", () => InputManager.Click(MouseButton.Left));
// Slider gets priority due to end time.
AddAssert("first selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(firstObject));
}
[Test]
public void TestCyclicSelection()
{
var firstObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 0 };
var secondObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 300 };
var thirdObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 600 };
AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[] { firstObject, secondObject, thirdObject }));
moveMouseToObject(() => firstObject);
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("first selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(firstObject));
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("second selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(secondObject));
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("third selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(thirdObject));
// cycle around
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("first selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(firstObject));
}
[Test]
public void TestCyclicSelectionOutwards()
{
var firstObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 0 };
var secondObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 300 };
var thirdObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 600 };
AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[] { firstObject, secondObject, thirdObject }));
moveMouseToObject(() => firstObject);
AddStep("seek near second", () => EditorClock.Seek(320));
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("second selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(secondObject));
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("third selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(thirdObject));
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("first selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(firstObject));
// cycle around
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("second selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(secondObject));
}
[Test]
public void TestCyclicSelectionBackwards()
{
var firstObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 0 };
var secondObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 300 };
var thirdObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 600 };
AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[] { firstObject, secondObject, thirdObject }));
moveMouseToObject(() => firstObject);
AddStep("seek to third", () => EditorClock.Seek(600));
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("third selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(thirdObject));
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("second selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(secondObject));
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("first selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(firstObject));
// cycle around
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("third selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(thirdObject));
}
[Test]
public void TestDoubleClickToSeek()
{
var hitCircle = new HitCircle { Position = new Vector2(256, 192), StartTime = 600 };
AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[] { hitCircle }));
moveMouseToObject(() => hitCircle);
AddRepeatStep("double click", () => InputManager.Click(MouseButton.Left), 2);
AddUntilStep("seeked to circle", () => EditorClock.CurrentTime, () => Is.EqualTo(600));
}
[TestCase(false)] [TestCase(false)]
[TestCase(true)] [TestCase(true)]
public void TestMultiSelectFromDrag(bool alreadySelectedBeforeDrag) public void TestMultiSelectFromDrag(bool alreadySelectedBeforeDrag)
{ {
HitCircle[] addedObjects = null; HitCircle[] addedObjects = null!;
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[] AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[]
{ {
@ -320,7 +480,7 @@ namespace osu.Game.Tests.Visual.Editing
[Test] [Test]
public void TestQuickDeleteRemovesSliderControlPoint() public void TestQuickDeleteRemovesSliderControlPoint()
{ {
Slider slider = null; Slider slider = null!;
PathControlPoint[] points = PathControlPoint[] points =
{ {

View File

@ -117,9 +117,9 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("move mouse to overlapping toggle button", () => AddStep("move mouse to overlapping toggle button", () =>
{ {
var playfield = hitObjectComposer.Playfield.ScreenSpaceDrawQuad; var playfield = hitObjectComposer.Playfield.ScreenSpaceDrawQuad;
var button = toolboxContainer.ChildrenOfType<DrawableTernaryButton>().First(b => playfield.Contains(b.ScreenSpaceDrawQuad.Centre)); var button = toolboxContainer.ChildrenOfType<DrawableTernaryButton>().First(b => playfield.Contains(getOverlapPoint(b)));
InputManager.MoveMouseTo(button); InputManager.MoveMouseTo(getOverlapPoint(button));
}); });
AddAssert("no circles placed", () => editorBeatmap.HitObjects.Count == 0); AddAssert("no circles placed", () => editorBeatmap.HitObjects.Count == 0);
@ -127,6 +127,12 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("attempt place circle", () => InputManager.Click(MouseButton.Left)); AddStep("attempt place circle", () => InputManager.Click(MouseButton.Left));
AddAssert("no circles placed", () => editorBeatmap.HitObjects.Count == 0); AddAssert("no circles placed", () => editorBeatmap.HitObjects.Count == 0);
Vector2 getOverlapPoint(DrawableTernaryButton ternaryButton)
{
var quad = ternaryButton.ScreenSpaceDrawQuad;
return quad.TopLeft + new Vector2(quad.Width * 9 / 10, quad.Height / 2);
}
} }
[Test] [Test]

View File

@ -23,7 +23,7 @@ namespace osu.Game.Tests.Visual.Editing
{ {
BeatDivisor.Value = 4; BeatDivisor.Value = 4;
Add(new BeatDivisorControl(BeatDivisor) Add(new BeatDivisorControl
{ {
Anchor = Anchor.TopRight, Anchor = Anchor.TopRight,
Origin = Anchor.TopRight, Origin = Anchor.TopRight,

View File

@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private HUDOverlay hudOverlay = null!; private HUDOverlay hudOverlay = null!;
[Cached(typeof(ScoreProcessor))] [Cached(typeof(ScoreProcessor))]
private ScoreProcessor scoreProcessor => gameplayState.ScoreProcessor; private ScoreProcessor scoreProcessor { get; set; }
[Cached(typeof(HealthProcessor))] [Cached(typeof(HealthProcessor))]
private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); private HealthProcessor healthProcessor = new DrainingHealthProcessor(0);
@ -47,6 +47,11 @@ namespace osu.Game.Tests.Visual.Gameplay
private Drawable hideTarget => hudOverlay.ChildrenOfType<SkinComponentsContainer>().First(); private Drawable hideTarget => hudOverlay.ChildrenOfType<SkinComponentsContainer>().First();
private Drawable keyCounterFlow => hudOverlay.ChildrenOfType<KeyCounterDisplay>().First().ChildrenOfType<FillFlowContainer<KeyCounter>>().Single(); private Drawable keyCounterFlow => hudOverlay.ChildrenOfType<KeyCounterDisplay>().First().ChildrenOfType<FillFlowContainer<KeyCounter>>().Single();
public TestSceneHUDOverlay()
{
scoreProcessor = gameplayState.ScoreProcessor;
}
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {

View File

@ -138,24 +138,28 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test] [Test]
public void TestCyclicSelection() public void TestCyclicSelection()
{ {
SkinBlueprint[] blueprints = null!; List<SkinBlueprint> blueprints = new List<SkinBlueprint>();
AddStep("Add big black boxes", () => AddStep("clear list", () => blueprints.Clear());
for (int i = 0; i < 3; i++)
{ {
InputManager.MoveMouseTo(skinEditor.ChildrenOfType<BigBlackBox>().First()); AddStep("Add big black box", () =>
InputManager.Click(MouseButton.Left); {
InputManager.Click(MouseButton.Left); InputManager.MoveMouseTo(skinEditor.ChildrenOfType<BigBlackBox>().First());
InputManager.Click(MouseButton.Left); InputManager.Click(MouseButton.Left);
}); });
AddStep("store box", () =>
{
// Add blueprints one-by-one so we have a stable order for testing reverse cyclic selection against.
blueprints.Add(skinEditor.ChildrenOfType<SkinBlueprint>().Single(s => s.IsSelected));
});
}
AddAssert("Three black boxes added", () => targetContainer.Components.OfType<BigBlackBox>().Count(), () => Is.EqualTo(3)); AddAssert("Three black boxes added", () => targetContainer.Components.OfType<BigBlackBox>().Count(), () => Is.EqualTo(3));
AddStep("Store black box blueprints", () => AddAssert("Selection is last", () => skinEditor.SelectedComponents.Single(), () => Is.EqualTo(blueprints[2].Item));
{
blueprints = skinEditor.ChildrenOfType<SkinBlueprint>().Where(b => b.Item is BigBlackBox).ToArray();
});
AddAssert("Selection is black box 1", () => skinEditor.SelectedComponents.Single(), () => Is.EqualTo(blueprints[0].Item));
AddStep("move cursor to black box", () => AddStep("move cursor to black box", () =>
{ {
@ -164,13 +168,13 @@ namespace osu.Game.Tests.Visual.Gameplay
}); });
AddStep("click on black box stack", () => InputManager.Click(MouseButton.Left)); AddStep("click on black box stack", () => InputManager.Click(MouseButton.Left));
AddAssert("Selection is black box 2", () => skinEditor.SelectedComponents.Single(), () => Is.EqualTo(blueprints[1].Item)); AddAssert("Selection is second last", () => skinEditor.SelectedComponents.Single(), () => Is.EqualTo(blueprints[1].Item));
AddStep("click on black box stack", () => InputManager.Click(MouseButton.Left)); AddStep("click on black box stack", () => InputManager.Click(MouseButton.Left));
AddAssert("Selection is black box 3", () => skinEditor.SelectedComponents.Single(), () => Is.EqualTo(blueprints[2].Item)); AddAssert("Selection is last", () => skinEditor.SelectedComponents.Single(), () => Is.EqualTo(blueprints[0].Item));
AddStep("click on black box stack", () => InputManager.Click(MouseButton.Left)); AddStep("click on black box stack", () => InputManager.Click(MouseButton.Left));
AddAssert("Selection is black box 1", () => skinEditor.SelectedComponents.Single(), () => Is.EqualTo(blueprints[0].Item)); AddAssert("Selection is first", () => skinEditor.SelectedComponents.Single(), () => Is.EqualTo(blueprints[2].Item));
AddStep("select all boxes", () => AddStep("select all boxes", () =>
{ {

View File

@ -23,7 +23,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public partial class TestSceneSkinEditorMultipleSkins : SkinnableTestScene public partial class TestSceneSkinEditorMultipleSkins : SkinnableTestScene
{ {
[Cached(typeof(ScoreProcessor))] [Cached(typeof(ScoreProcessor))]
private ScoreProcessor scoreProcessor => gameplayState.ScoreProcessor; private ScoreProcessor scoreProcessor { get; set; }
[Cached(typeof(HealthProcessor))] [Cached(typeof(HealthProcessor))]
private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); private HealthProcessor healthProcessor = new DrainingHealthProcessor(0);
@ -37,6 +37,11 @@ namespace osu.Game.Tests.Visual.Gameplay
[Cached] [Cached]
public readonly EditorClipboard Clipboard = new EditorClipboard(); public readonly EditorClipboard Clipboard = new EditorClipboard();
public TestSceneSkinEditorMultipleSkins()
{
scoreProcessor = gameplayState.ScoreProcessor;
}
[SetUpSteps] [SetUpSteps]
public void SetUpSteps() public void SetUpSteps()
{ {

View File

@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private HUDOverlay hudOverlay; private HUDOverlay hudOverlay;
[Cached(typeof(ScoreProcessor))] [Cached(typeof(ScoreProcessor))]
private ScoreProcessor scoreProcessor => gameplayState.ScoreProcessor; private ScoreProcessor scoreProcessor { get; set; }
[Cached(typeof(HealthProcessor))] [Cached(typeof(HealthProcessor))]
private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); private HealthProcessor healthProcessor = new DrainingHealthProcessor(0);
@ -47,6 +47,11 @@ namespace osu.Game.Tests.Visual.Gameplay
private Drawable hideTarget => hudOverlay.ChildrenOfType<SkinComponentsContainer>().First(); private Drawable hideTarget => hudOverlay.ChildrenOfType<SkinComponentsContainer>().First();
private Drawable keyCounterFlow => hudOverlay.ChildrenOfType<KeyCounterDisplay>().First().ChildrenOfType<FillFlowContainer<KeyCounter>>().Single(); private Drawable keyCounterFlow => hudOverlay.ChildrenOfType<KeyCounterDisplay>().First().ChildrenOfType<FillFlowContainer<KeyCounter>>().Single();
public TestSceneSkinnableHUDOverlay()
{
scoreProcessor = gameplayState.ScoreProcessor;
}
[Test] [Test]
public void TestComboCounterIncrementing() public void TestComboCounterIncrementing()
{ {

View File

@ -67,6 +67,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("wait for present", () => songSelect.IsCurrentScreen() && songSelect.BeatmapSetsLoaded); AddUntilStep("wait for present", () => songSelect.IsCurrentScreen() && songSelect.BeatmapSetsLoaded);
} }
[Test]
public void TestSelectFreeMods()
{
AddStep("set some freemods", () => songSelect.FreeMods.Value = new OsuRuleset().GetModsFor(ModType.Fun).ToArray());
AddStep("set all freemods", () => songSelect.FreeMods.Value = new OsuRuleset().CreateAllMods().ToArray());
AddStep("set no freemods", () => songSelect.FreeMods.Value = Array.Empty<Mod>());
}
[Test] [Test]
public void TestBeatmapConfirmed() public void TestBeatmapConfirmed()
{ {

View File

@ -722,6 +722,30 @@ namespace osu.Game.Tests.Visual.Navigation
AddUntilStep("Wait for game exit", () => Game.ScreenStack.CurrentScreen == null); AddUntilStep("Wait for game exit", () => Game.ScreenStack.CurrentScreen == null);
} }
[Test]
public void TestForceExitWithOperationInProgress()
{
AddStep("set hold delay to 0", () => Game.LocalConfig.SetValue(OsuSetting.UIHoldActivationDelay, 0.0));
AddUntilStep("wait for dialog overlay", () => Game.ChildrenOfType<DialogOverlay>().SingleOrDefault() != null);
AddStep("start ongoing operation", () =>
{
Game.Notifications.Post(new ProgressNotification
{
Text = "Something is still running",
Progress = 0.5f,
State = ProgressNotificationState.Active,
});
});
AddStep("attempt exit", () =>
{
for (int i = 0; i < 2; ++i)
Game.ScreenStack.CurrentScreen.Exit();
});
AddUntilStep("stopped at exit confirm", () => Game.ChildrenOfType<DialogOverlay>().Single().CurrentDialog is ConfirmExitDialog);
}
[Test] [Test]
public void TestExitGameFromSongSelect() public void TestExitGameFromSongSelect()
{ {

View File

@ -12,7 +12,7 @@ namespace osu.Game.Tournament.Tests
[STAThread] [STAThread]
public static int Main(string[] args) public static int Main(string[] args)
{ {
using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true })) using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu-development", new HostOptions { BindIPC = true }))
{ {
host.Run(new TournamentTestBrowser()); host.Run(new TournamentTestBrowser());
return 0; return 0;

View File

@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Platform; using osu.Framework.Platform;
@ -43,6 +45,6 @@ namespace osu.Game.Tournament.IO
Logger.Log("Changing tournament storage: " + GetFullPath(string.Empty)); Logger.Log("Changing tournament storage: " + GetFullPath(string.Empty));
} }
public IEnumerable<string> ListTournaments() => AllTournaments.GetDirectories(string.Empty); public IEnumerable<string> ListTournaments() => AllTournaments.GetDirectories(string.Empty).OrderBy(directory => directory, StringComparer.CurrentCultureIgnoreCase);
} }
} }

View File

@ -25,12 +25,11 @@ namespace osu.Game.Tournament
{ {
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
InternalChild = new Container InternalChild = new CircularContainer
{ {
Anchor = Anchor.BottomRight, Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight, Origin = Anchor.BottomRight,
Position = new Vector2(5), Position = new Vector2(-5),
CornerRadius = 10,
Masking = true, Masking = true,
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Children = new Drawable[] Children = new Drawable[]
@ -43,18 +42,10 @@ namespace osu.Game.Tournament
saveChangesButton = new TourneyButton saveChangesButton = new TourneyButton
{ {
Text = "Save Changes", Text = "Save Changes",
RelativeSizeAxes = Axes.None,
Width = 140, Width = 140,
Height = 50, Height = 50,
Padding = new MarginPadding Margin = new MarginPadding(10),
{
Top = 10,
Left = 10,
},
Margin = new MarginPadding
{
Right = 10,
Bottom = 10,
},
Action = saveChanges, Action = saveChanges,
// Enabled = { Value = false }, // Enabled = { Value = false },
}, },

View File

@ -14,6 +14,7 @@ using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Input.States; using osu.Framework.Input.States;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Tournament.Components;
using osu.Game.Tournament.Models; using osu.Game.Tournament.Models;
using osu.Game.Tournament.Screens.Ladder; using osu.Game.Tournament.Screens.Ladder;
using osu.Game.Tournament.Screens.Ladder.Components; using osu.Game.Tournament.Screens.Ladder.Components;
@ -35,11 +36,9 @@ namespace osu.Game.Tournament.Screens.Editors
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
Content.Add(new LadderEditorSettings AddInternal(new ControlPanel
{ {
Anchor = Anchor.TopRight, Child = new LadderEditorSettings(),
Origin = Anchor.TopRight,
Margin = new MarginPadding(5)
}); });
AddInternal(rightClickMessage = new WarningBox("Right click to place and link matches")); AddInternal(rightClickMessage = new WarningBox("Right click to place and link matches"));

View File

@ -11,10 +11,9 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Online.API;
using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings;
using osu.Game.Tournament.Components;
using osu.Game.Tournament.Models; using osu.Game.Tournament.Models;
using osu.Game.Tournament.Screens.Drawings.Components;
using osu.Game.Users; using osu.Game.Users;
using osuTK; using osuTK;
@ -59,8 +58,6 @@ namespace osu.Game.Tournament.Screens.Editors
{ {
public TournamentTeam Model { get; } public TournamentTeam Model { get; }
private readonly Container drawableContainer;
[Resolved] [Resolved]
private TournamentSceneManager? sceneManager { get; set; } private TournamentSceneManager? sceneManager { get; set; }
@ -74,10 +71,10 @@ namespace osu.Game.Tournament.Screens.Editors
Masking = true; Masking = true;
CornerRadius = 10; CornerRadius = 10;
PlayerEditor playerEditor = new PlayerEditor(Model) RelativeSizeAxes = Axes.X;
{ AutoSizeAxes = Axes.Y;
Width = 0.95f
}; PlayerEditor playerEditor = new PlayerEditor(Model);
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
@ -86,17 +83,17 @@ namespace osu.Game.Tournament.Screens.Editors
Colour = OsuColour.Gray(0.1f), Colour = OsuColour.Gray(0.1f),
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}, },
drawableContainer = new Container new GroupTeam(team)
{ {
Size = new Vector2(100, 50), Margin = new MarginPadding(16),
Margin = new MarginPadding(10), Scale = new Vector2(2),
Anchor = Anchor.TopRight, Anchor = Anchor.TopRight,
Origin = Anchor.TopRight, Origin = Anchor.TopRight,
}, },
new FillFlowContainer new FillFlowContainer
{ {
Margin = new MarginPadding(5),
Spacing = new Vector2(5), Spacing = new Vector2(5),
Padding = new MarginPadding(10),
Direction = FillDirection.Full, Direction = FillDirection.Full,
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
@ -133,25 +130,6 @@ namespace osu.Game.Tournament.Screens.Editors
Current = Model.LastYearPlacing Current = Model.LastYearPlacing
}, },
new SettingsButton new SettingsButton
{
Width = 0.11f,
Margin = new MarginPadding(10),
Text = "Add player",
Action = () => playerEditor.CreateNew()
},
new DangerousSettingsButton
{
Width = 0.11f,
Text = "Delete Team",
Margin = new MarginPadding(10),
Action = () =>
{
Expire();
ladderInfo.Teams.Remove(Model);
},
},
playerEditor,
new SettingsButton
{ {
Width = 0.2f, Width = 0.2f,
Margin = new MarginPadding(10), Margin = new MarginPadding(10),
@ -161,19 +139,35 @@ namespace osu.Game.Tournament.Screens.Editors
sceneManager?.SetScreen(new SeedingEditorScreen(team, parent)); sceneManager?.SetScreen(new SeedingEditorScreen(team, parent));
} }
}, },
playerEditor,
new SettingsButton
{
Text = "Add player",
Action = () => playerEditor.CreateNew()
},
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
new DangerousSettingsButton
{
Width = 0.2f,
Text = "Delete Team",
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Action = () =>
{
Expire();
ladderInfo.Teams.Remove(Model);
},
},
}
},
} }
}, },
}; };
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
Model.FlagName.BindValueChanged(updateDrawable, true);
}
private void updateDrawable(ValueChangedEvent<string> flag)
{
drawableContainer.Child = new DrawableTeamFlag(Model);
} }
public partial class PlayerEditor : CompositeDrawable public partial class PlayerEditor : CompositeDrawable
@ -193,6 +187,8 @@ namespace osu.Game.Tournament.Screens.Editors
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical, Direction = FillDirection.Vertical,
Padding = new MarginPadding(5),
Spacing = new Vector2(5),
ChildrenEnumerable = team.Players.Select(p => new PlayerRow(team, p)) ChildrenEnumerable = team.Players.Select(p => new PlayerRow(team, p))
}; };
} }
@ -208,27 +204,22 @@ namespace osu.Game.Tournament.Screens.Editors
{ {
private readonly TournamentUser user; private readonly TournamentUser user;
[Resolved]
protected IAPIProvider API { get; private set; } = null!;
[Resolved] [Resolved]
private TournamentGameBase game { get; set; } = null!; private TournamentGameBase game { get; set; } = null!;
private readonly Bindable<int?> playerId = new Bindable<int?>(); private readonly Bindable<int?> playerId = new Bindable<int?>();
private readonly Container drawableContainer; private readonly Container userPanelContainer;
public PlayerRow(TournamentTeam team, TournamentUser user) public PlayerRow(TournamentTeam team, TournamentUser user)
{ {
this.user = user; this.user = user;
Margin = new MarginPadding(10);
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y; AutoSizeAxes = Axes.Y;
Masking = true; Masking = true;
CornerRadius = 5; CornerRadius = 10;
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
@ -240,10 +231,11 @@ namespace osu.Game.Tournament.Screens.Editors
new FillFlowContainer new FillFlowContainer
{ {
Margin = new MarginPadding(5), Margin = new MarginPadding(5),
Padding = new MarginPadding { Right = 160 }, Padding = new MarginPadding { Right = 60 },
Spacing = new Vector2(5), Spacing = new Vector2(5),
Direction = FillDirection.Horizontal, Direction = FillDirection.Horizontal,
AutoSizeAxes = Axes.Both, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[] Children = new Drawable[]
{ {
new SettingsNumberBox new SettingsNumberBox
@ -253,9 +245,10 @@ namespace osu.Game.Tournament.Screens.Editors
Width = 200, Width = 200,
Current = playerId, Current = playerId,
}, },
drawableContainer = new Container userPanelContainer = new Container
{ {
Size = new Vector2(100, 70), Width = 400,
RelativeSizeAxes = Axes.Y,
}, },
} }
}, },
@ -298,7 +291,12 @@ namespace osu.Game.Tournament.Screens.Editors
private void updatePanel() => Scheduler.AddOnce(() => private void updatePanel() => Scheduler.AddOnce(() =>
{ {
drawableContainer.Child = new UserGridPanel(user.ToAPIUser()) { Width = 300 }; userPanelContainer.Child = new UserListPanel(user.ToAPIUser())
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Scale = new Vector2(1f),
};
}); });
} }
} }

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
@ -15,7 +13,6 @@ using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Overlays.Settings;
using osu.Game.Tournament.Components; using osu.Game.Tournament.Components;
using osuTK; using osuTK;
@ -27,23 +24,24 @@ namespace osu.Game.Tournament.Screens.Editors
{ {
protected abstract BindableList<TModel> Storage { get; } protected abstract BindableList<TModel> Storage { get; }
private FillFlowContainer<TDrawable> flow; private FillFlowContainer<TDrawable> flow = null!;
[Resolved(canBeNull: true)] [Resolved]
private TournamentSceneManager sceneManager { get; set; } private TournamentSceneManager? sceneManager { get; set; }
protected ControlPanel ControlPanel; protected ControlPanel ControlPanel = null!;
private readonly TournamentScreen parentScreen; private readonly TournamentScreen? parentScreen;
private BackButton backButton;
protected TournamentEditorScreen(TournamentScreen parentScreen = null) private BackButton backButton = null!;
protected TournamentEditorScreen(TournamentScreen? parentScreen = null)
{ {
this.parentScreen = parentScreen; this.parentScreen = parentScreen;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load(OsuColour colours)
{ {
AddRangeInternal(new Drawable[] AddRangeInternal(new Drawable[]
{ {
@ -63,6 +61,7 @@ namespace osu.Game.Tournament.Screens.Editors
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
Spacing = new Vector2(20), Spacing = new Vector2(20),
Padding = new MarginPadding(20),
}, },
}, },
ControlPanel = new ControlPanel ControlPanel = new ControlPanel
@ -75,9 +74,10 @@ namespace osu.Game.Tournament.Screens.Editors
Text = "Add new", Text = "Add new",
Action = () => Storage.Add(new TModel()) Action = () => Storage.Add(new TModel())
}, },
new DangerousSettingsButton new TourneyButton
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
BackgroundColour = colours.Pink3,
Text = "Clear all", Text = "Clear all",
Action = Storage.Clear Action = Storage.Clear
}, },

View File

@ -213,7 +213,8 @@ namespace osu.Game.Tournament.Screens.Ladder.Components
int instantWinAmount = Match.Round.Value.BestOf.Value / 2; int instantWinAmount = Match.Round.Value.BestOf.Value / 2;
Match.Completed.Value = Match.Round.Value.BestOf.Value > 0 Match.Completed.Value = Match.Round.Value.BestOf.Value > 0
&& (Match.Team1Score.Value + Match.Team2Score.Value >= Match.Round.Value.BestOf.Value || Match.Team1Score.Value > instantWinAmount || Match.Team2Score.Value > instantWinAmount); && (Match.Team1Score.Value + Match.Team2Score.Value >= Match.Round.Value.BestOf.Value || Match.Team1Score.Value > instantWinAmount
|| Match.Team2Score.Value > instantWinAmount);
} }
protected override void LoadComplete() protected override void LoadComplete()
@ -265,8 +266,6 @@ namespace osu.Game.Tournament.Screens.Ladder.Components
protected override bool OnMouseDown(MouseDownEvent e) => e.Button == MouseButton.Left && editorInfo != null; protected override bool OnMouseDown(MouseDownEvent e) => e.Button == MouseButton.Left && editorInfo != null;
protected override bool OnDragStart(DragStartEvent e) => editorInfo != null;
protected override bool OnKeyDown(KeyDownEvent e) protected override bool OnKeyDown(KeyDownEvent e)
{ {
if (Selected && editorInfo != null && e.Key == Key.Delete) if (Selected && editorInfo != null && e.Key == Key.Delete)
@ -287,17 +286,36 @@ namespace osu.Game.Tournament.Screens.Ladder.Components
return true; return true;
} }
private Vector2 positionAtStartOfDrag;
protected override bool OnDragStart(DragStartEvent e)
{
if (editorInfo != null)
{
positionAtStartOfDrag = Position;
return true;
}
return false;
}
protected override void OnDrag(DragEvent e) protected override void OnDrag(DragEvent e)
{ {
base.OnDrag(e); base.OnDrag(e);
Selected = true; Selected = true;
this.MoveToOffset(e.Delta);
var pos = Position; this.MoveTo(snapToGrid(positionAtStartOfDrag + (e.MousePosition - e.MouseDownPosition)));
Match.Position.Value = new Point((int)pos.X, (int)pos.Y);
Match.Position.Value = new Point((int)Position.X, (int)Position.Y);
} }
private Vector2 snapToGrid(Vector2 pos) =>
new Vector2(
(int)(pos.X / 10) * 10,
(int)(pos.Y / 10) * 10
);
public void Remove() public void Remove()
{ {
Selected = false; Selected = false;

View File

@ -11,15 +11,17 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings;
using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Screens.Play.PlayerSettings;
using osu.Game.Tournament.Components; using osu.Game.Tournament.Components;
using osu.Game.Tournament.Models; using osu.Game.Tournament.Models;
using osuTK;
namespace osu.Game.Tournament.Screens.Ladder.Components namespace osu.Game.Tournament.Screens.Ladder.Components
{ {
public partial class LadderEditorSettings : PlayerSettingsGroup public partial class LadderEditorSettings : CompositeDrawable
{ {
private SettingsDropdown<TournamentRound> roundDropdown; private SettingsDropdown<TournamentRound> roundDropdown;
private PlayerCheckbox losersCheckbox; private PlayerCheckbox losersCheckbox;
@ -33,21 +35,26 @@ namespace osu.Game.Tournament.Screens.Ladder.Components
[Resolved] [Resolved]
private LadderInfo ladderInfo { get; set; } private LadderInfo ladderInfo { get; set; }
public LadderEditorSettings()
: base("ladder")
{
}
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
Children = new Drawable[] RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
InternalChild = new FillFlowContainer
{ {
team1Dropdown = new SettingsTeamDropdown(ladderInfo.Teams) { LabelText = "Team 1" }, RelativeSizeAxes = Axes.X,
team2Dropdown = new SettingsTeamDropdown(ladderInfo.Teams) { LabelText = "Team 2" }, AutoSizeAxes = Axes.Y,
roundDropdown = new SettingsRoundDropdown(ladderInfo.Rounds) { LabelText = "Round" }, Direction = FillDirection.Vertical,
losersCheckbox = new PlayerCheckbox { LabelText = "Losers Bracket" }, Spacing = new Vector2(5),
dateTimeBox = new DateTextBox { LabelText = "Match Time" }, Children = new Drawable[]
{
team1Dropdown = new SettingsTeamDropdown(ladderInfo.Teams) { LabelText = "Team 1" },
team2Dropdown = new SettingsTeamDropdown(ladderInfo.Teams) { LabelText = "Team 2" },
roundDropdown = new SettingsRoundDropdown(ladderInfo.Rounds) { LabelText = "Round" },
losersCheckbox = new PlayerCheckbox { LabelText = "Losers Bracket" },
dateTimeBox = new DateTextBox { LabelText = "Match Time" },
},
}; };
editorInfo.Selected.ValueChanged += selection => editorInfo.Selected.ValueChanged += selection =>

View File

@ -36,8 +36,8 @@ namespace osu.Game.Tournament.Screens.Ladder
{ {
float newScale = Math.Clamp(scale + e.ScrollDelta.Y / 15 * scale, min_scale, max_scale); float newScale = Math.Clamp(scale + e.ScrollDelta.Y / 15 * scale, min_scale, max_scale);
this.MoveTo(target -= e.MousePosition * (newScale - scale), 2000, Easing.OutQuint); this.MoveTo(target -= e.MousePosition * (newScale - scale), 1000, Easing.OutQuint);
this.ScaleTo(scale = newScale, 2000, Easing.OutQuint); this.ScaleTo(scale = newScale, 1000, Easing.OutQuint);
return true; return true;
} }

View File

@ -41,6 +41,7 @@ namespace osu.Game.Tournament.Screens.Ladder
InternalChild = Content = new Container InternalChild = Content = new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Masking = true,
Children = new Drawable[] Children = new Drawable[]
{ {
new TourneyVideo("ladder") new TourneyVideo("ladder")

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System; using System;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -15,7 +13,15 @@ namespace osu.Game.Tournament.Screens.Setup
{ {
internal partial class ActionableInfo : LabelledDrawable<Drawable> internal partial class ActionableInfo : LabelledDrawable<Drawable>
{ {
protected OsuButton Button; public const float BUTTON_SIZE = 120;
public Action? Action;
protected FillFlowContainer FlowContainer = null!;
protected OsuButton Button = null!;
private TournamentSpriteText valueText = null!;
public ActionableInfo() public ActionableInfo()
: base(true) : base(true)
@ -37,11 +43,6 @@ namespace osu.Game.Tournament.Screens.Setup
set => valueText.Colour = value ? Color4.Red : Color4.White; set => valueText.Colour = value ? Color4.Red : Color4.White;
} }
public Action Action;
private TournamentSpriteText valueText;
protected FillFlowContainer FlowContainer;
protected override Drawable CreateComponent() => new Container protected override Drawable CreateComponent() => new Container
{ {
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
@ -63,7 +64,7 @@ namespace osu.Game.Tournament.Screens.Setup
{ {
Button = new RoundedButton Button = new RoundedButton
{ {
Size = new Vector2(100, 40), Size = new Vector2(BUTTON_SIZE, 40),
Action = () => Action?.Invoke() Action = () => Action?.Invoke()
} }
} }

View File

@ -106,8 +106,8 @@ namespace osu.Game.Tournament.Screens.Setup
loginOverlay.State.Value = Visibility.Visible; loginOverlay.State.Value = Visibility.Visible;
}, },
Value = api?.LocalUser.Value.Username, Value = api.LocalUser.Value.Username,
Failing = api?.IsLoggedIn != true, Failing = api.IsLoggedIn != true,
Description = "In order to access the API and display metadata, signing in is required." Description = "In order to access the API and display metadata, signing in is required."
}, },
new LabelledDropdown<RulesetInfo> new LabelledDropdown<RulesetInfo>

View File

@ -15,6 +15,7 @@ namespace osu.Game.Tournament.Screens.Setup
{ {
private OsuDropdown<string> dropdown; private OsuDropdown<string> dropdown;
private OsuButton folderButton; private OsuButton folderButton;
private OsuButton reloadTournamentsButton;
[Resolved] [Resolved]
private TournamentGameBase game { get; set; } private TournamentGameBase game { get; set; }
@ -28,6 +29,8 @@ namespace osu.Game.Tournament.Screens.Setup
dropdown.Items = storage.ListTournaments(); dropdown.Items = storage.ListTournaments();
dropdown.Current.BindValueChanged(v => Button.Enabled.Value = v.NewValue != startupTournament, true); dropdown.Current.BindValueChanged(v => Button.Enabled.Value = v.NewValue != startupTournament, true);
reloadTournamentsButton.Action = () => dropdown.Items = storage.ListTournaments();
Action = () => Action = () =>
{ {
game.RestartAppWhenExited(); game.RestartAppWhenExited();
@ -45,10 +48,16 @@ namespace osu.Game.Tournament.Screens.Setup
FlowContainer.Insert(-1, folderButton = new RoundedButton FlowContainer.Insert(-1, folderButton = new RoundedButton
{ {
Text = "Open folder", Text = "Open folder",
Width = 100 Width = BUTTON_SIZE
}); });
FlowContainer.Insert(-2, dropdown = new OsuDropdown<string> FlowContainer.Insert(-2, reloadTournamentsButton = new RoundedButton
{
Text = "Refresh",
Width = BUTTON_SIZE
});
FlowContainer.Insert(-3, dropdown = new OsuDropdown<string>
{ {
Width = 510 Width = 510
}); });

View File

@ -38,7 +38,7 @@ namespace osu.Game.Tournament
private Container screens; private Container screens;
private TourneyVideo video; private TourneyVideo video;
public const float CONTROL_AREA_WIDTH = 160; public const float CONTROL_AREA_WIDTH = 200;
public const float STREAM_AREA_WIDTH = 1366; public const float STREAM_AREA_WIDTH = 1366;
@ -252,14 +252,13 @@ namespace osu.Game.Tournament
if (shortcutKey != null) if (shortcutKey != null)
{ {
Add(new Container Add(new CircularContainer
{ {
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
Size = new Vector2(24), Size = new Vector2(24),
Margin = new MarginPadding(5), Margin = new MarginPadding(5),
Masking = true, Masking = true,
CornerRadius = 4,
Alpha = 0.5f, Alpha = 0.5f,
Blending = BlendingParameters.Additive, Blending = BlendingParameters.Additive,
Children = new Drawable[] Children = new Drawable[]

View File

@ -1,18 +1,21 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings;
namespace osu.Game.Tournament namespace osu.Game.Tournament
{ {
public partial class TourneyButton : OsuButton public partial class TourneyButton : SettingsButton
{ {
public new Box Background => base.Background; public new Box Background => base.Background;
public TourneyButton() [BackgroundDependencyLoader]
: base(null) private void load()
{ {
Padding = new MarginPadding();
} }
} }
} }

3
osu.Game/FodyWeavers.xml Normal file
View File

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<Realm DisableAnalytics="true" />
</Weavers>

View File

@ -20,16 +20,16 @@ namespace osu.Game.Graphics.UserInterface
{ {
public const float HEIGHT = 15; public const float HEIGHT = 15;
public const float EXPANDED_SIZE = 50; public const float DEFAULT_EXPANDED_SIZE = 50;
private const float border_width = 3; private const float border_width = 3;
private readonly Box fill; private readonly Box fill;
private readonly Container main; private readonly Container main;
public Nub() public Nub(float expandedSize = DEFAULT_EXPANDED_SIZE)
{ {
Size = new Vector2(EXPANDED_SIZE, HEIGHT); Size = new Vector2(expandedSize, HEIGHT);
InternalChildren = new[] InternalChildren = new[]
{ {

View File

@ -47,7 +47,7 @@ namespace osu.Game.Graphics.UserInterface
private Sample sampleChecked; private Sample sampleChecked;
private Sample sampleUnchecked; private Sample sampleUnchecked;
public OsuCheckbox(bool nubOnRight = true) public OsuCheckbox(bool nubOnRight = true, float nubSize = Nub.DEFAULT_EXPANDED_SIZE)
{ {
AutoSizeAxes = Axes.Y; AutoSizeAxes = Axes.Y;
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
@ -61,7 +61,7 @@ namespace osu.Game.Graphics.UserInterface
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
}, },
Nub = new Nub(), Nub = new Nub(nubSize),
new HoverSounds() new HoverSounds()
}; };
@ -70,14 +70,14 @@ namespace osu.Game.Graphics.UserInterface
Nub.Anchor = Anchor.CentreRight; Nub.Anchor = Anchor.CentreRight;
Nub.Origin = Anchor.CentreRight; Nub.Origin = Anchor.CentreRight;
Nub.Margin = new MarginPadding { Right = nub_padding }; Nub.Margin = new MarginPadding { Right = nub_padding };
LabelTextFlowContainer.Padding = new MarginPadding { Right = Nub.EXPANDED_SIZE + nub_padding * 2 }; LabelTextFlowContainer.Padding = new MarginPadding { Right = Nub.DEFAULT_EXPANDED_SIZE + nub_padding * 2 };
} }
else else
{ {
Nub.Anchor = Anchor.CentreLeft; Nub.Anchor = Anchor.CentreLeft;
Nub.Origin = Anchor.CentreLeft; Nub.Origin = Anchor.CentreLeft;
Nub.Margin = new MarginPadding { Left = nub_padding }; Nub.Margin = new MarginPadding { Left = nub_padding };
LabelTextFlowContainer.Padding = new MarginPadding { Left = Nub.EXPANDED_SIZE + nub_padding * 2 }; LabelTextFlowContainer.Padding = new MarginPadding { Left = Nub.DEFAULT_EXPANDED_SIZE + nub_padding * 2 };
} }
Nub.Current.BindTo(Current); Nub.Current.BindTo(Current);

View File

@ -51,7 +51,7 @@ namespace osu.Game.Graphics.UserInterface
public RoundedSliderBar() public RoundedSliderBar()
{ {
Height = Nub.HEIGHT; Height = Nub.HEIGHT;
RangePadding = Nub.EXPANDED_SIZE / 2; RangePadding = Nub.DEFAULT_EXPANDED_SIZE / 2;
Children = new Drawable[] Children = new Drawable[]
{ {
new Container new Container

View File

@ -229,7 +229,7 @@ namespace osu.Game.Overlays.Dialog
{ {
// Buttons are regularly added in BDL or LoadComplete, so let's schedule to ensure // Buttons are regularly added in BDL or LoadComplete, so let's schedule to ensure
// they are ready to be pressed. // they are ready to be pressed.
Schedule(() => Buttons.OfType<T>().First().TriggerClick()); Scheduler.AddOnce(() => Buttons.OfType<T>().FirstOrDefault()?.TriggerClick());
} }
protected override bool OnKeyDown(KeyDownEvent e) protected override bool OnKeyDown(KeyDownEvent e)

View File

@ -111,7 +111,7 @@ namespace osu.Game.Overlays.Mods
private readonly Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> globalAvailableMods = new Bindable<Dictionary<ModType, IReadOnlyList<Mod>>>(); private readonly Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> globalAvailableMods = new Bindable<Dictionary<ModType, IReadOnlyList<Mod>>>();
private IEnumerable<ModState> allAvailableMods => AvailableMods.Value.SelectMany(pair => pair.Value); public IEnumerable<ModState> AllAvailableMods => AvailableMods.Value.SelectMany(pair => pair.Value);
private readonly BindableBool customisationVisible = new BindableBool(); private readonly BindableBool customisationVisible = new BindableBool();
@ -382,7 +382,7 @@ namespace osu.Game.Overlays.Mods
private void filterMods() private void filterMods()
{ {
foreach (var modState in allAvailableMods) foreach (var modState in AllAvailableMods)
modState.ValidForSelection.Value = modState.Mod.HasImplementation && IsValidMod.Invoke(modState.Mod); modState.ValidForSelection.Value = modState.Mod.HasImplementation && IsValidMod.Invoke(modState.Mod);
} }
@ -407,7 +407,7 @@ namespace osu.Game.Overlays.Mods
bool anyCustomisableModActive = false; bool anyCustomisableModActive = false;
bool anyModPendingConfiguration = false; bool anyModPendingConfiguration = false;
foreach (var modState in allAvailableMods) foreach (var modState in AllAvailableMods)
{ {
anyCustomisableModActive |= modState.Active.Value && modState.Mod.GetSettingsSourceProperties().Any(); anyCustomisableModActive |= modState.Active.Value && modState.Mod.GetSettingsSourceProperties().Any();
anyModPendingConfiguration |= modState.PendingConfiguration; anyModPendingConfiguration |= modState.PendingConfiguration;
@ -464,7 +464,7 @@ namespace osu.Game.Overlays.Mods
var newSelection = new List<Mod>(); var newSelection = new List<Mod>();
foreach (var modState in allAvailableMods) foreach (var modState in AllAvailableMods)
{ {
var matchingSelectedMod = SelectedMods.Value.SingleOrDefault(selected => selected.GetType() == modState.Mod.GetType()); var matchingSelectedMod = SelectedMods.Value.SingleOrDefault(selected => selected.GetType() == modState.Mod.GetType());
@ -491,7 +491,7 @@ namespace osu.Game.Overlays.Mods
if (externalSelectionUpdateInProgress) if (externalSelectionUpdateInProgress)
return; return;
var candidateSelection = allAvailableMods.Where(modState => modState.Active.Value) var candidateSelection = AllAvailableMods.Where(modState => modState.Active.Value)
.Select(modState => modState.Mod) .Select(modState => modState.Mod)
.ToArray(); .ToArray();

View File

@ -196,7 +196,7 @@ namespace osu.Game.Overlays.Settings
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
Spacing = new Vector2(0, 10), Spacing = new Vector2(0, 5),
Child = Control = CreateControl(), Child = Control = CreateControl(),
} }
} }

View File

@ -36,7 +36,6 @@ namespace osu.Game.Overlays.Settings
{ {
numberBox = new OutlinedNumberBox numberBox = new OutlinedNumberBox
{ {
Margin = new MarginPadding { Top = 5 },
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
CommitOnFocusLost = true CommitOnFocusLost = true
} }

View File

@ -25,8 +25,6 @@ namespace osu.Game.Overlays.SkinEditor
[Resolved] [Resolved]
private SkinEditor editor { get; set; } = null!; private SkinEditor editor { get; set; } = null!;
protected override bool AllowCyclicSelection => true;
public SkinBlueprintContainer(ISerialisableDrawableContainer targetContainer) public SkinBlueprintContainer(ISerialisableDrawableContainer targetContainer)
{ {
this.targetContainer = targetContainer; this.targetContainer = targetContainer;

View File

@ -117,13 +117,10 @@ namespace osu.Game.Rulesets.Edit
{ {
PlayfieldContentContainer = new Container PlayfieldContentContainer = new Container
{ {
Name = "Content", Name = "Playfield content",
Padding = new MarginPadding RelativeSizeAxes = Axes.Y,
{ Anchor = Anchor.Centre,
Left = TOOLBOX_CONTRACTED_SIZE_LEFT, Origin = Anchor.Centre,
Right = TOOLBOX_CONTRACTED_SIZE_RIGHT,
},
RelativeSizeAxes = Axes.Both,
Children = new Drawable[] Children = new Drawable[]
{ {
// layers below playfield // layers below playfield
@ -240,6 +237,14 @@ namespace osu.Game.Rulesets.Edit
}); });
} }
protected override void Update()
{
base.Update();
// Ensure that the playfield is always centered but also doesn't get cut off by toolboxes.
PlayfieldContentContainer.Width = Math.Max(1024, DrawWidth) - TOOLBOX_CONTRACTED_SIZE_RIGHT * 2;
}
public override Playfield Playfield => drawableRulesetWrapper.Playfield; public override Playfield Playfield => drawableRulesetWrapper.Playfield;
public override IEnumerable<DrawableHitObject> HitObjects => drawableRulesetWrapper.Playfield.AllHitObjects; public override IEnumerable<DrawableHitObject> HitObjects => drawableRulesetWrapper.Playfield.AllHitObjects;
@ -472,7 +477,7 @@ namespace osu.Game.Rulesets.Edit
public abstract partial class HitObjectComposer : CompositeDrawable, IPositionSnapProvider public abstract partial class HitObjectComposer : CompositeDrawable, IPositionSnapProvider
{ {
public const float TOOLBOX_CONTRACTED_SIZE_LEFT = 60; public const float TOOLBOX_CONTRACTED_SIZE_LEFT = 60;
public const float TOOLBOX_CONTRACTED_SIZE_RIGHT = 130; public const float TOOLBOX_CONTRACTED_SIZE_RIGHT = 120;
public readonly Ruleset Ruleset; public readonly Ruleset Ruleset;

View File

@ -384,5 +384,10 @@ namespace osu.Game.Rulesets
/// Can be overridden to add a ruleset-specific section to the editor beatmap setup screen. /// Can be overridden to add a ruleset-specific section to the editor beatmap setup screen.
/// </summary> /// </summary>
public virtual RulesetSetupSection? CreateEditorSetupSection() => null; public virtual RulesetSetupSection? CreateEditorSetupSection() => null;
/// <summary>
/// Can be overridden to alter the difficulty section to the editor beatmap setup screen.
/// </summary>
public virtual DifficultySection? CreateEditorDifficultySection() => null;
} }
} }

View File

@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Rulesets.Edit;
using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components;
using osu.Game.Screens.Edit.Components.Timelines.Summary; using osu.Game.Screens.Edit.Components.Timelines.Summary;
using osuTK; using osuTK;
@ -57,7 +58,7 @@ namespace osu.Game.Screens.Edit
new Dimension(GridSizeMode.Absolute, 170), new Dimension(GridSizeMode.Absolute, 170),
new Dimension(), new Dimension(),
new Dimension(GridSizeMode.Absolute, 220), new Dimension(GridSizeMode.Absolute, 220),
new Dimension(GridSizeMode.Absolute, 120), new Dimension(GridSizeMode.Absolute, HitObjectComposer.TOOLBOX_CONTRACTED_SIZE_RIGHT),
}, },
Content = new[] Content = new[]
{ {
@ -69,7 +70,6 @@ namespace osu.Game.Screens.Edit
TestGameplayButton = new TestGameplayButton TestGameplayButton = new TestGameplayButton
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = 10 },
Size = new Vector2(1), Size = new Vector2(1),
Action = editor.TestGameplay, Action = editor.TestGameplay,
} }

View File

@ -3,6 +3,9 @@
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Graphics; using osu.Game.Graphics;
@ -14,19 +17,59 @@ namespace osu.Game.Screens.Edit.Components.Menus
{ {
public partial class EditorMenuBar : OsuMenu public partial class EditorMenuBar : OsuMenu
{ {
private const float heading_area = 114;
public EditorMenuBar() public EditorMenuBar()
: base(Direction.Horizontal, true) : base(Direction.Horizontal, true)
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
MaskingContainer.CornerRadius = 0; MaskingContainer.CornerRadius = 0;
ItemsContainer.Padding = new MarginPadding { Left = 100 }; ItemsContainer.Padding = new MarginPadding();
ContentContainer.Margin = new MarginPadding { Left = heading_area };
ContentContainer.Masking = true;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider) private void load(OverlayColourProvider colourProvider, TextureStore textures)
{ {
BackgroundColour = colourProvider.Background3; BackgroundColour = colourProvider.Background3;
TextFlowContainer text;
AddRangeInternal(new[]
{
new Container
{
RelativeSizeAxes = Axes.Y,
Width = heading_area,
Padding = new MarginPadding(8),
Children = new Drawable[]
{
new Sprite
{
Size = new Vector2(26),
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Texture = textures.Get("Icons/Hexacons/editor"),
},
text = new TextFlowContainer
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
AutoSizeAxes = Axes.Both,
}
}
},
});
text.AddText(@"osu!", t => t.Font = OsuFont.TorusAlternate);
text.AddText(@"editor", t =>
{
t.Font = OsuFont.TorusAlternate;
t.Colour = colourProvider.Highlight1;
});
} }
protected override Framework.Graphics.UserInterface.Menu CreateSubMenu() => new SubMenu(); protected override Framework.Graphics.UserInterface.Menu CreateSubMenu() => new SubMenu();

View File

@ -73,6 +73,8 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
{ {
public MarkerVisualisation() public MarkerVisualisation()
{ {
const float box_height = 4;
Anchor = Anchor.CentreLeft; Anchor = Anchor.CentreLeft;
Origin = Anchor.Centre; Origin = Anchor.Centre;
RelativePositionAxes = Axes.X; RelativePositionAxes = Axes.X;
@ -80,32 +82,46 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
AutoSizeAxes = Axes.X; AutoSizeAxes = Axes.X;
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
new Box
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Size = new Vector2(14, box_height),
},
new Triangle new Triangle
{ {
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.BottomCentre, Origin = Anchor.BottomCentre,
Scale = new Vector2(1, -1), Scale = new Vector2(1, -1),
Size = new Vector2(10, 5), Size = new Vector2(10, 5),
Y = box_height,
}, },
new Triangle new Triangle
{ {
Anchor = Anchor.BottomCentre, Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre, Origin = Anchor.BottomCentre,
Size = new Vector2(10, 5) Size = new Vector2(10, 5),
Y = -box_height,
},
new Box
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Size = new Vector2(14, box_height),
}, },
new Box new Box
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Y,
Width = 2, Width = 1.4f,
EdgeSmoothness = new Vector2(1, 0) EdgeSmoothness = new Vector2(1, 0)
} }
}; };
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) => Colour = colours.Red; private void load(OsuColour colours) => Colour = colours.Red1;
} }
} }
} }

View File

@ -33,12 +33,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
{ {
private int? lastCustomDivisor; private int? lastCustomDivisor;
private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); [Resolved]
private BindableBeatDivisor beatDivisor { get; set; } = null!;
public BeatDivisorControl(BindableBeatDivisor beatDivisor)
{
this.beatDivisor.BindTo(beatDivisor);
}
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider) private void load(OverlayColourProvider colourProvider)

View File

@ -46,15 +46,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected readonly BindableList<T> SelectedItems = new BindableList<T>(); protected readonly BindableList<T> SelectedItems = new BindableList<T>();
/// <summary>
/// Whether to allow cyclic selection on clicking multiple times.
/// </summary>
/// <remarks>
/// Disabled by default as it does not work well with editors that support double-clicking or other advanced interactions.
/// Can probably be made to work with more thought.
/// </remarks>
protected virtual bool AllowCyclicSelection => false;
protected BlueprintContainer() protected BlueprintContainer()
{ {
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
@ -167,6 +158,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
if (ClickedBlueprint == null || SelectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered) != ClickedBlueprint) if (ClickedBlueprint == null || SelectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered) != ClickedBlueprint)
return false; return false;
doubleClickHandled = true;
return true; return true;
} }
@ -177,6 +169,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
{ {
endClickSelection(e); endClickSelection(e);
clickSelectionHandled = false; clickSelectionHandled = false;
doubleClickHandled = false;
isDraggingBlueprint = false; isDraggingBlueprint = false;
wasDragStarted = false; wasDragStarted = false;
}); });
@ -376,11 +369,22 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// </summary> /// </summary>
private bool clickSelectionHandled; private bool clickSelectionHandled;
/// <summary>
/// Whether a blueprint was double-clicked since last mouse down.
/// </summary>
private bool doubleClickHandled;
/// <summary> /// <summary>
/// Whether the selected blueprint(s) were already selected on mouse down. Generally used to perform selection cycling on mouse up in such a case. /// Whether the selected blueprint(s) were already selected on mouse down. Generally used to perform selection cycling on mouse up in such a case.
/// </summary> /// </summary>
private bool selectedBlueprintAlreadySelectedOnMouseDown; private bool selectedBlueprintAlreadySelectedOnMouseDown;
/// <summary>
/// Sorts the supplied <paramref name="blueprints"/> by the order of preference when making a selection.
/// Blueprints at the start of the list will be prioritised over later items if the selection requested is ambiguous due to spatial overlap.
/// </summary>
protected virtual IEnumerable<SelectionBlueprint<T>> ApplySelectionOrder(IEnumerable<SelectionBlueprint<T>> blueprints) => blueprints.Reverse();
/// <summary> /// <summary>
/// Attempts to select any hovered blueprints. /// Attempts to select any hovered blueprints.
/// </summary> /// </summary>
@ -390,15 +394,28 @@ namespace osu.Game.Screens.Edit.Compose.Components
{ {
// Iterate from the top of the input stack (blueprints closest to the front of the screen first). // Iterate from the top of the input stack (blueprints closest to the front of the screen first).
// Priority is given to already-selected blueprints. // Priority is given to already-selected blueprints.
foreach (SelectionBlueprint<T> blueprint in SelectionBlueprints.AliveChildren.Reverse().OrderByDescending(b => b.IsSelected)) foreach (SelectionBlueprint<T> blueprint in SelectionBlueprints.AliveChildren.Where(b => b.IsSelected))
{ {
if (!blueprint.IsHovered) continue; if (runForBlueprint(blueprint))
return true;
}
selectedBlueprintAlreadySelectedOnMouseDown = blueprint.State == SelectionState.Selected; foreach (SelectionBlueprint<T> blueprint in ApplySelectionOrder(SelectionBlueprints.AliveChildren))
return clickSelectionHandled = SelectionHandler.MouseDownSelectionRequested(blueprint, e); {
if (runForBlueprint(blueprint))
return true;
} }
return false; return false;
bool runForBlueprint(SelectionBlueprint<T> blueprint)
{
if (!blueprint.IsHovered) return false;
selectedBlueprintAlreadySelectedOnMouseDown = blueprint.State == SelectionState.Selected;
clickSelectionHandled = SelectionHandler.MouseDownSelectionRequested(blueprint, e);
return true;
}
} }
/// <summary> /// <summary>
@ -408,8 +425,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// <returns>Whether a click selection was active.</returns> /// <returns>Whether a click selection was active.</returns>
private bool endClickSelection(MouseButtonEvent e) private bool endClickSelection(MouseButtonEvent e)
{ {
// If already handled a selection or drag, we don't want to perform a mouse up / click action. // If already handled a selection, double-click, or drag, we don't want to perform a mouse up / click action.
if (clickSelectionHandled || isDraggingBlueprint) return true; if (clickSelectionHandled || doubleClickHandled || isDraggingBlueprint) return true;
if (e.Button != MouseButton.Left) return false; if (e.Button != MouseButton.Left) return false;
@ -425,20 +442,20 @@ namespace osu.Game.Screens.Edit.Compose.Components
return false; return false;
} }
if (!wasDragStarted && selectedBlueprintAlreadySelectedOnMouseDown && SelectedItems.Count == 1 && AllowCyclicSelection) if (!wasDragStarted && selectedBlueprintAlreadySelectedOnMouseDown && SelectedItems.Count == 1)
{ {
// If a click occurred and was handled by the currently selected blueprint but didn't result in a drag, // If a click occurred and was handled by the currently selected blueprint but didn't result in a drag,
// cycle between other blueprints which are also under the cursor. // cycle between other blueprints which are also under the cursor.
// The depth of blueprints is constantly changing (see above where selected blueprints are brought to the front). // The depth of blueprints is constantly changing (see above where selected blueprints are brought to the front).
// For this logic, we want a stable sort order so we can correctly cycle, thus using the blueprintMap instead. // For this logic, we want a stable sort order so we can correctly cycle, thus using the blueprintMap instead.
IEnumerable<SelectionBlueprint<T>> cyclingSelectionBlueprints = blueprintMap.Values; IEnumerable<SelectionBlueprint<T>> cyclingSelectionBlueprints = ApplySelectionOrder(blueprintMap.Values);
// If there's already a selection, let's start from the blueprint after the selection. // If there's already a selection, let's start from the blueprint after the selection.
cyclingSelectionBlueprints = cyclingSelectionBlueprints.SkipWhile(b => !b.IsSelected).Skip(1); cyclingSelectionBlueprints = cyclingSelectionBlueprints.SkipWhile(b => !b.IsSelected).Skip(1);
// Add the blueprints from before the selection to the end of the enumerable to allow for cyclic selection. // Add the blueprints from before the selection to the end of the enumerable to allow for cyclic selection.
cyclingSelectionBlueprints = cyclingSelectionBlueprints.Concat(blueprintMap.Values.TakeWhile(b => !b.IsSelected)); cyclingSelectionBlueprints = cyclingSelectionBlueprints.Concat(ApplySelectionOrder(blueprintMap.Values).TakeWhile(b => !b.IsSelected));
foreach (SelectionBlueprint<T> blueprint in cyclingSelectionBlueprints) foreach (SelectionBlueprint<T> blueprint in cyclingSelectionBlueprints)
{ {

View File

@ -3,6 +3,7 @@
#nullable disable #nullable disable
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
@ -129,6 +130,10 @@ namespace osu.Game.Screens.Edit.Compose.Components
return true; return true;
} }
protected override IEnumerable<SelectionBlueprint<HitObject>> ApplySelectionOrder(IEnumerable<SelectionBlueprint<HitObject>> blueprints) =>
base.ApplySelectionOrder(blueprints)
.OrderBy(b => Math.Min(Math.Abs(EditorClock.CurrentTime - b.Item.GetEndTime()), Math.Abs(EditorClock.CurrentTime - b.Item.StartTime)));
protected override Container<SelectionBlueprint<HitObject>> CreateSelectionBlueprintContainer() => new HitObjectOrderedSelectionContainer { RelativeSizeAxes = Axes.Both }; protected override Container<SelectionBlueprint<HitObject>> CreateSelectionBlueprintContainer() => new HitObjectOrderedSelectionContainer { RelativeSizeAxes = Axes.Both };
protected override SelectionHandler<HitObject> CreateSelectionHandler() => new EditorSelectionHandler(); protected override SelectionHandler<HitObject> CreateSelectionHandler() => new EditorSelectionHandler();

View File

@ -12,17 +12,17 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{ {
public partial class CentreMarker : CompositeDrawable public partial class CentreMarker : CompositeDrawable
{ {
private const float triangle_width = 15; private const float triangle_width = 8;
private const float triangle_height = 10;
private const float bar_width = 2; private const float bar_width = 1.6f;
public CentreMarker() public CentreMarker()
{ {
RelativeSizeAxes = Axes.Y; RelativeSizeAxes = Axes.Y;
Size = new Vector2(triangle_width, 1); Size = new Vector2(triangle_width, 1);
Anchor = Anchor.Centre; Anchor = Anchor.TopCentre;
Origin = Anchor.Centre; Origin = Anchor.TopCentre;
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
@ -37,22 +37,16 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{ {
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.BottomCentre, Origin = Anchor.BottomCentre,
Size = new Vector2(triangle_width, triangle_height), Size = new Vector2(triangle_width, triangle_width * 0.8f),
Scale = new Vector2(1, -1) Scale = new Vector2(1, -1)
}, },
new Triangle
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Size = new Vector2(triangle_width, triangle_height),
}
}; };
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
Colour = colours.RedDark; Colour = colours.Red1;
} }
} }
} }

View File

@ -1,63 +1,68 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Localisation; using osu.Game.Localisation;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Resources.Localisation.Web; using osu.Game.Resources.Localisation.Web;
using osu.Game.Rulesets.Edit;
using osuTK; using osuTK;
namespace osu.Game.Screens.Edit.Compose.Components.Timeline namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{ {
public partial class TimelineArea : CompositeDrawable public partial class TimelineArea : CompositeDrawable
{ {
public Timeline Timeline; public Timeline Timeline = null!;
private readonly Drawable userContent; private readonly Drawable userContent;
public TimelineArea(Drawable content = null) public TimelineArea(Drawable? content = null)
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y; AutoSizeAxes = Axes.Y;
userContent = content ?? Drawable.Empty(); userContent = content ?? Empty();
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider) private void load(OverlayColourProvider colourProvider, OsuColour colours)
{ {
Masking = true;
OsuCheckbox waveformCheckbox; OsuCheckbox waveformCheckbox;
OsuCheckbox controlPointsCheckbox; OsuCheckbox controlPointsCheckbox;
OsuCheckbox ticksCheckbox; OsuCheckbox ticksCheckbox;
const float padding = 10;
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background5
},
new GridContainer new GridContainer
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
},
ColumnDimensions = new[]
{
new Dimension(GridSizeMode.Absolute, 135),
new Dimension(),
new Dimension(GridSizeMode.Absolute, 35),
new Dimension(GridSizeMode.Absolute, HitObjectComposer.TOOLBOX_CONTRACTED_SIZE_RIGHT - padding * 2),
},
Content = new[] Content = new[]
{ {
new Drawable[] new Drawable[]
{ {
new Container new Container
{ {
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Both,
AutoSizeAxes = Axes.X,
Name = @"Toggle controls", Name = @"Toggle controls",
Children = new Drawable[] Children = new Drawable[]
{ {
@ -68,24 +73,23 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
}, },
new FillFlowContainer new FillFlowContainer
{ {
AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Both,
Width = 160, Padding = new MarginPadding(padding),
Padding = new MarginPadding(10),
Direction = FillDirection.Vertical, Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 4), Spacing = new Vector2(0, 4),
Children = new[] Children = new[]
{ {
waveformCheckbox = new OsuCheckbox waveformCheckbox = new OsuCheckbox(nubSize: 30f)
{ {
LabelText = EditorStrings.TimelineWaveform, LabelText = EditorStrings.TimelineWaveform,
Current = { Value = true }, Current = { Value = true },
}, },
ticksCheckbox = new OsuCheckbox ticksCheckbox = new OsuCheckbox(nubSize: 30f)
{ {
LabelText = EditorStrings.TimelineTicks, LabelText = EditorStrings.TimelineTicks,
Current = { Value = true }, Current = { Value = true },
}, },
controlPointsCheckbox = new OsuCheckbox controlPointsCheckbox = new OsuCheckbox(nubSize: 30f)
{ {
LabelText = BeatmapsetsStrings.ShowStatsBpm, LabelText = BeatmapsetsStrings.ShowStatsBpm,
Current = { Value = true }, Current = { Value = true },
@ -96,29 +100,52 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
}, },
new Container new Container
{ {
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.X, AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
// the out-of-bounds portion of the centre marker.
new Box
{
Width = 24,
Height = EditorScreenWithTimeline.PADDING,
Depth = float.MaxValue,
Colour = colours.Red1,
Anchor = Anchor.TopCentre,
Origin = Anchor.BottomCentre,
},
new Box
{
RelativeSizeAxes = Axes.Both,
Depth = float.MaxValue,
Colour = colourProvider.Background5
},
Timeline = new Timeline(userContent),
}
},
new Container
{
RelativeSizeAxes = Axes.Both,
Name = @"Zoom controls", Name = @"Zoom controls",
Padding = new MarginPadding { Right = padding },
Children = new Drawable[] Children = new Drawable[]
{ {
new Box new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background3, Colour = colourProvider.Background2,
}, },
new Container<TimelineButton> new Container<TimelineButton>
{ {
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Both,
AutoSizeAxes = Axes.X,
Masking = true,
Children = new[] Children = new[]
{ {
new TimelineButton new TimelineButton
{ {
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Both,
Height = 0.5f, Size = new Vector2(1, 0.5f),
Icon = FontAwesome.Solid.SearchPlus, Icon = FontAwesome.Solid.SearchPlus,
Action = () => Timeline.AdjustZoomRelatively(1) Action = () => Timeline.AdjustZoomRelatively(1)
}, },
@ -126,8 +153,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{ {
Anchor = Anchor.BottomLeft, Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft, Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Both,
Height = 0.5f, Size = new Vector2(1, 0.5f),
Icon = FontAwesome.Solid.SearchMinus, Icon = FontAwesome.Solid.SearchMinus,
Action = () => Timeline.AdjustZoomRelatively(-1) Action = () => Timeline.AdjustZoomRelatively(-1)
}, },
@ -135,19 +162,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
} }
} }
}, },
Timeline = new Timeline(userContent), new BeatDivisorControl { RelativeSizeAxes = Axes.Both }
}, },
}, },
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
},
ColumnDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
new Dimension(GridSizeMode.AutoSize),
new Dimension(),
}
} }
}; };

View File

@ -22,7 +22,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
RelativeSizeAxes = Axes.Y; RelativeSizeAxes = Axes.Y;
AutoSizeAxes = Axes.X; AutoSizeAxes = Axes.X;
Origin = Anchor.TopCentre; Origin = Anchor.TopLeft;
X = (float)group.Time; X = (float)group.Time;
} }

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -10,24 +8,25 @@ using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Edit.Compose.Components.Timeline namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{ {
public partial class TopPointPiece : CompositeDrawable public partial class TopPointPiece : CompositeDrawable
{ {
private readonly ControlPoint point; protected readonly ControlPoint Point;
protected OsuSpriteText Label { get; private set; } protected OsuSpriteText Label { get; private set; } = null!;
private const float width = 80;
public TopPointPiece(ControlPoint point) public TopPointPiece(ControlPoint point)
{ {
this.point = point; Point = point;
AutoSizeAxes = Axes.X; Width = width;
Height = 16; Height = 16;
Margin = new MarginPadding(4); Margin = new MarginPadding { Vertical = 4 };
Masking = true;
CornerRadius = Height / 2;
Origin = Anchor.TopCentre; Origin = Anchor.TopCentre;
Anchor = Anchor.TopCentre; Anchor = Anchor.TopCentre;
@ -36,17 +35,52 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
const float corner_radius = 4;
const float arrow_extension = 3;
const float triangle_portion = 15;
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
new Box // This is a triangle, trust me.
// Doing it this way looks okay. Doing it using Triangle primitive is basically impossible.
new Container
{ {
Colour = point.GetRepresentingColour(colours), Colour = Point.GetRepresentingColour(colours),
RelativeSizeAxes = Axes.Both, X = -corner_radius,
Size = new Vector2(triangle_portion * arrow_extension, Height),
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Masking = true,
CornerRadius = Height,
CornerExponent = 1.4f,
Children = new Drawable[]
{
new Box
{
Colour = Color4.White,
RelativeSizeAxes = Axes.Both,
},
}
},
new Container
{
RelativeSizeAxes = Axes.Y,
Width = width - triangle_portion,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Colour = Point.GetRepresentingColour(colours),
Masking = true,
CornerRadius = corner_radius,
Child = new Box
{
Colour = Color4.White,
RelativeSizeAxes = Axes.Both,
},
}, },
Label = new OsuSpriteText Label = new OsuSpriteText
{ {
Anchor = Anchor.Centre, Anchor = Anchor.CentreLeft,
Origin = Anchor.Centre, Origin = Anchor.CentreLeft,
Padding = new MarginPadding(3), Padding = new MarginPadding(3),
Font = OsuFont.Default.With(size: 14, weight: FontWeight.SemiBold), Font = OsuFont.Default.With(size: 14, weight: FontWeight.SemiBold),
Colour = colours.B5, Colour = colours.B5,

View File

@ -359,7 +359,7 @@ namespace osu.Game.Screens.Edit
{ {
Anchor = Anchor.BottomRight, Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight, Origin = Anchor.BottomRight,
X = -15, X = -10,
Current = Mode, Current = Mode,
}, },
}, },

View File

@ -1,43 +1,37 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Screens.Edit.Compose.Components;
using osu.Game.Screens.Edit.Compose.Components.Timeline; using osu.Game.Screens.Edit.Compose.Components.Timeline;
namespace osu.Game.Screens.Edit namespace osu.Game.Screens.Edit
{ {
public abstract partial class EditorScreenWithTimeline : EditorScreen public abstract partial class EditorScreenWithTimeline : EditorScreen
{ {
private const float padding = 10; public const float PADDING = 10;
private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); private Container timelineContainer = null!;
private Container timelineContainer; private Container mainContent = null!;
private LoadingSpinner spinner = null!;
protected EditorScreenWithTimeline(EditorScreenMode type) protected EditorScreenWithTimeline(EditorScreenMode type)
: base(type) : base(type)
{ {
} }
private Container mainContent;
private LoadingSpinner spinner;
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]
private void load(OverlayColourProvider colourProvider, [CanBeNull] BindableBeatDivisor beatDivisor) private void load(OverlayColourProvider colourProvider)
{ {
if (beatDivisor != null) // Grid with only two rows.
this.beatDivisor.BindTo(beatDivisor); // First is the timeline area, which should be allowed to expand as required.
// Second is the main editor content, including the playfield and side toolbars (but not the bottom).
Child = new GridContainer Child = new GridContainer
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
@ -67,7 +61,7 @@ namespace osu.Game.Screens.Edit
Name = "Timeline content", Name = "Timeline content",
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
Padding = new MarginPadding { Horizontal = padding, Top = padding }, Padding = new MarginPadding { Horizontal = PADDING, Top = PADDING },
Child = new GridContainer Child = new GridContainer
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
@ -80,9 +74,7 @@ namespace osu.Game.Screens.Edit
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
Padding = new MarginPadding { Right = 5 },
}, },
new BeatDivisorControl(this.beatDivisor) { RelativeSizeAxes = Axes.Both }
}, },
}, },
RowDimensions = new[] RowDimensions = new[]

View File

@ -13,14 +13,14 @@ using osu.Game.Localisation;
namespace osu.Game.Screens.Edit.Setup namespace osu.Game.Screens.Edit.Setup
{ {
internal partial class DifficultySection : SetupSection public partial class DifficultySection : SetupSection
{ {
private LabelledSliderBar<float> circleSizeSlider = null!; protected LabelledSliderBar<float> CircleSizeSlider { get; private set; } = null!;
private LabelledSliderBar<float> healthDrainSlider = null!; protected LabelledSliderBar<float> HealthDrainSlider { get; private set; } = null!;
private LabelledSliderBar<float> approachRateSlider = null!; protected LabelledSliderBar<float> ApproachRateSlider { get; private set; } = null!;
private LabelledSliderBar<float> overallDifficultySlider = null!; protected LabelledSliderBar<float> OverallDifficultySlider { get; private set; } = null!;
private LabelledSliderBar<double> baseVelocitySlider = null!; protected LabelledSliderBar<double> BaseVelocitySlider { get; private set; } = null!;
private LabelledSliderBar<double> tickRateSlider = null!; protected LabelledSliderBar<double> TickRateSlider { get; private set; } = null!;
public override LocalisableString Title => EditorSetupStrings.DifficultyHeader; public override LocalisableString Title => EditorSetupStrings.DifficultyHeader;
@ -29,7 +29,7 @@ namespace osu.Game.Screens.Edit.Setup
{ {
Children = new Drawable[] Children = new Drawable[]
{ {
circleSizeSlider = new LabelledSliderBar<float> CircleSizeSlider = new LabelledSliderBar<float>
{ {
Label = BeatmapsetsStrings.ShowStatsCs, Label = BeatmapsetsStrings.ShowStatsCs,
FixedLabelWidth = LABEL_WIDTH, FixedLabelWidth = LABEL_WIDTH,
@ -42,7 +42,7 @@ namespace osu.Game.Screens.Edit.Setup
Precision = 0.1f, Precision = 0.1f,
} }
}, },
healthDrainSlider = new LabelledSliderBar<float> HealthDrainSlider = new LabelledSliderBar<float>
{ {
Label = BeatmapsetsStrings.ShowStatsDrain, Label = BeatmapsetsStrings.ShowStatsDrain,
FixedLabelWidth = LABEL_WIDTH, FixedLabelWidth = LABEL_WIDTH,
@ -55,7 +55,7 @@ namespace osu.Game.Screens.Edit.Setup
Precision = 0.1f, Precision = 0.1f,
} }
}, },
approachRateSlider = new LabelledSliderBar<float> ApproachRateSlider = new LabelledSliderBar<float>
{ {
Label = BeatmapsetsStrings.ShowStatsAr, Label = BeatmapsetsStrings.ShowStatsAr,
FixedLabelWidth = LABEL_WIDTH, FixedLabelWidth = LABEL_WIDTH,
@ -68,7 +68,7 @@ namespace osu.Game.Screens.Edit.Setup
Precision = 0.1f, Precision = 0.1f,
} }
}, },
overallDifficultySlider = new LabelledSliderBar<float> OverallDifficultySlider = new LabelledSliderBar<float>
{ {
Label = BeatmapsetsStrings.ShowStatsAccuracy, Label = BeatmapsetsStrings.ShowStatsAccuracy,
FixedLabelWidth = LABEL_WIDTH, FixedLabelWidth = LABEL_WIDTH,
@ -81,7 +81,7 @@ namespace osu.Game.Screens.Edit.Setup
Precision = 0.1f, Precision = 0.1f,
} }
}, },
baseVelocitySlider = new LabelledSliderBar<double> BaseVelocitySlider = new LabelledSliderBar<double>
{ {
Label = EditorSetupStrings.BaseVelocity, Label = EditorSetupStrings.BaseVelocity,
FixedLabelWidth = LABEL_WIDTH, FixedLabelWidth = LABEL_WIDTH,
@ -94,7 +94,7 @@ namespace osu.Game.Screens.Edit.Setup
Precision = 0.01f, Precision = 0.01f,
} }
}, },
tickRateSlider = new LabelledSliderBar<double> TickRateSlider = new LabelledSliderBar<double>
{ {
Label = EditorSetupStrings.TickRate, Label = EditorSetupStrings.TickRate,
FixedLabelWidth = LABEL_WIDTH, FixedLabelWidth = LABEL_WIDTH,
@ -120,12 +120,12 @@ namespace osu.Game.Screens.Edit.Setup
{ {
// for now, update these on commit rather than making BeatmapMetadata bindables. // for now, update these on commit rather than making BeatmapMetadata bindables.
// after switching database engines we can reconsider if switching to bindables is a good direction. // after switching database engines we can reconsider if switching to bindables is a good direction.
Beatmap.Difficulty.CircleSize = circleSizeSlider.Current.Value; Beatmap.Difficulty.CircleSize = CircleSizeSlider.Current.Value;
Beatmap.Difficulty.DrainRate = healthDrainSlider.Current.Value; Beatmap.Difficulty.DrainRate = HealthDrainSlider.Current.Value;
Beatmap.Difficulty.ApproachRate = approachRateSlider.Current.Value; Beatmap.Difficulty.ApproachRate = ApproachRateSlider.Current.Value;
Beatmap.Difficulty.OverallDifficulty = overallDifficultySlider.Current.Value; Beatmap.Difficulty.OverallDifficulty = OverallDifficultySlider.Current.Value;
Beatmap.Difficulty.SliderMultiplier = baseVelocitySlider.Current.Value; Beatmap.Difficulty.SliderMultiplier = BaseVelocitySlider.Current.Value;
Beatmap.Difficulty.SliderTickRate = tickRateSlider.Current.Value; Beatmap.Difficulty.SliderTickRate = TickRateSlider.Current.Value;
Beatmap.UpdateAllHitObjects(); Beatmap.UpdateAllHitObjects();
Beatmap.SaveState(); Beatmap.SaveState();

View File

@ -26,16 +26,18 @@ namespace osu.Game.Screens.Edit.Setup
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(EditorBeatmap beatmap, OverlayColourProvider colourProvider) private void load(EditorBeatmap beatmap, OverlayColourProvider colourProvider)
{ {
var ruleset = beatmap.BeatmapInfo.Ruleset.CreateInstance();
var sectionsEnumerable = new List<SetupSection> var sectionsEnumerable = new List<SetupSection>
{ {
new ResourcesSection(), new ResourcesSection(),
new MetadataSection(), new MetadataSection(),
new DifficultySection(), ruleset.CreateEditorDifficultySection() ?? new DifficultySection(),
new ColoursSection(), new ColoursSection(),
new DesignSection(), new DesignSection(),
}; };
var rulesetSpecificSection = beatmap.BeatmapInfo.Ruleset.CreateInstance().CreateEditorSetupSection(); var rulesetSpecificSection = ruleset.CreateEditorSetupSection();
if (rulesetSpecificSection != null) if (rulesetSpecificSection != null)
sectionsEnumerable.Add(rulesetSpecificSection); sectionsEnumerable.Add(rulesetSpecificSection);

View File

@ -1,15 +1,21 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
using osuTK; using osuTK;
@ -17,28 +23,60 @@ namespace osu.Game.Screens.OnlinePlay
{ {
public partial class FooterButtonFreeMods : FooterButton, IHasCurrentValue<IReadOnlyList<Mod>> public partial class FooterButtonFreeMods : FooterButton, IHasCurrentValue<IReadOnlyList<Mod>>
{ {
public Bindable<IReadOnlyList<Mod>> Current public Bindable<IReadOnlyList<Mod>> Current { get; set; } = new BindableWithCurrent<IReadOnlyList<Mod>>();
private OsuSpriteText count = null!;
private Circle circle = null!;
private readonly FreeModSelectOverlay freeModSelectOverlay;
public FooterButtonFreeMods(FreeModSelectOverlay freeModSelectOverlay)
{ {
get => modDisplay.Current; this.freeModSelectOverlay = freeModSelectOverlay;
set => modDisplay.Current = value;
} }
private readonly ModDisplay modDisplay; [Resolved]
private OsuColour colours { get; set; } = null!;
public FooterButtonFreeMods()
{
ButtonContentContainer.Add(modDisplay = new ModDisplay
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Scale = new Vector2(0.8f),
ExpansionMode = ExpansionMode.AlwaysContracted,
});
}
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load()
{ {
ButtonContentContainer.AddRange(new[]
{
new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
circle = new Circle
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Colour = colours.YellowDark,
RelativeSizeAxes = Axes.Both,
},
count = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Padding = new MarginPadding(5),
UseFullGlyphHeight = false,
}
}
},
new IconButton
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Scale = new Vector2(0.8f),
Icon = FontAwesome.Solid.Bars,
Action = () => freeModSelectOverlay.ToggleVisibility()
}
});
SelectedColour = colours.Yellow; SelectedColour = colours.Yellow;
DeselectedColour = SelectedColour.Opacity(0.5f); DeselectedColour = SelectedColour.Opacity(0.5f);
Text = @"freemods"; Text = @"freemods";
@ -49,14 +87,49 @@ namespace osu.Game.Screens.OnlinePlay
base.LoadComplete(); base.LoadComplete();
Current.BindValueChanged(_ => updateModDisplay(), true); Current.BindValueChanged(_ => updateModDisplay(), true);
// Overwrite any external behaviour as we delegate the main toggle action to a sub-button.
Action = toggleAllFreeMods;
}
/// <summary>
/// Immediately toggle all free mods on/off.
/// </summary>
private void toggleAllFreeMods()
{
var availableMods = allAvailableAndValidMods.ToArray();
Current.Value = Current.Value.Count == availableMods.Length
? Array.Empty<Mod>()
: availableMods;
} }
private void updateModDisplay() private void updateModDisplay()
{ {
if (Current.Value?.Count > 0) int current = Current.Value.Count;
modDisplay.FadeIn();
if (current == allAvailableAndValidMods.Count())
{
count.Text = "all";
count.FadeColour(colours.Gray2, 200, Easing.OutQuint);
circle.FadeColour(colours.Yellow, 200, Easing.OutQuint);
}
else if (current > 0)
{
count.Text = $"{current} mods";
count.FadeColour(colours.Gray2, 200, Easing.OutQuint);
circle.FadeColour(colours.YellowDark, 200, Easing.OutQuint);
}
else else
modDisplay.FadeOut(); {
count.Text = "off";
count.FadeColour(colours.GrayF, 200, Easing.OutQuint);
circle.FadeColour(colours.Gray4, 200, Easing.OutQuint);
}
} }
private IEnumerable<Mod> allAvailableAndValidMods => freeModSelectOverlay.AllAvailableMods
.Where(state => state.ValidForSelection.Value)
.Select(state => state.Mod);
} }
} }

View File

@ -23,6 +23,7 @@ using osu.Framework.Screens;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
using osu.Game.Online.API;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.Mods; using osu.Game.Overlays.Mods;
@ -76,6 +77,9 @@ namespace osu.Game.Screens.OnlinePlay.Match
[Resolved] [Resolved]
private RulesetStore rulesets { get; set; } private RulesetStore rulesets { get; set; }
[Resolved]
private IAPIProvider api { get; set; } = null!;
[Resolved(canBeNull: true)] [Resolved(canBeNull: true)]
protected OnlinePlayScreen ParentScreen { get; private set; } protected OnlinePlayScreen ParentScreen { get; private set; }
@ -284,6 +288,8 @@ namespace osu.Game.Screens.OnlinePlay.Match
[Resolved(canBeNull: true)] [Resolved(canBeNull: true)]
private IDialogOverlay dialogOverlay { get; set; } private IDialogOverlay dialogOverlay { get; set; }
protected virtual bool IsConnected => api.State.Value == APIState.Online;
public override bool OnBackButton() public override bool OnBackButton()
{ {
if (Room.RoomID.Value == null) if (Room.RoomID.Value == null)
@ -356,7 +362,12 @@ namespace osu.Game.Screens.OnlinePlay.Match
if (ExitConfirmed) if (ExitConfirmed)
return true; return true;
if (dialogOverlay == null || Room.RoomID.Value != null || Room.Playlist.Count == 0) if (!IsConnected)
return true;
bool hasUnsavedChanges = Room.RoomID.Value == null && Room.Playlist.Count > 0;
if (dialogOverlay == null || !hasUnsavedChanges)
return true; return true;
// if the dialog is already displayed, block exiting until the user explicitly makes a decision. // if the dialog is already displayed, block exiting until the user explicitly makes a decision.

View File

@ -41,7 +41,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
// To work around this, temporarily remove the room and trigger an immediate listing poll. // To work around this, temporarily remove the room and trigger an immediate listing poll.
if (e.Last is MultiplayerMatchSubScreen match) if (e.Last is MultiplayerMatchSubScreen match)
{ {
RoomManager.RemoveRoom(match.Room); RoomManager?.RemoveRoom(match.Room);
ListingPollingComponent.PollImmediately(); ListingPollingComponent.PollImmediately();
} }
} }

View File

@ -75,6 +75,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
handleRoomLost(); handleRoomLost();
} }
protected override bool IsConnected => base.IsConnected && client.IsConnected.Value;
protected override Drawable CreateMainContent() => new Container protected override Drawable CreateMainContent() => new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
@ -250,13 +252,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
public override bool OnExiting(ScreenExitEvent e) public override bool OnExiting(ScreenExitEvent e)
{ {
// the room may not be left immediately after a disconnection due to async flow, // room has not been created yet or we're offline; exit immediately.
// so checking the IsConnected status is also required. if (client.Room == null || !IsConnected)
if (client.Room == null || !client.IsConnected.Value)
{
// room has not been created yet; exit immediately.
return base.OnExiting(e); return base.OnExiting(e);
}
if (!exitConfirmed && dialogOverlay != null) if (!exitConfirmed && dialogOverlay != null)
{ {

View File

@ -175,9 +175,12 @@ namespace osu.Game.Screens.OnlinePlay
protected override IEnumerable<(FooterButton, OverlayContainer?)> CreateFooterButtons() protected override IEnumerable<(FooterButton, OverlayContainer?)> CreateFooterButtons()
{ {
var buttons = base.CreateFooterButtons().ToList(); var baseButtons = base.CreateFooterButtons().ToList();
buttons.Insert(buttons.FindIndex(b => b.Item1 is FooterButtonMods) + 1, (new FooterButtonFreeMods { Current = FreeMods }, freeModSelectOverlay)); var freeModsButton = new FooterButtonFreeMods(freeModSelectOverlay) { Current = FreeMods };
return buttons;
baseButtons.Insert(baseButtons.FindIndex(b => b.Item1 is FooterButtonMods) + 1, (freeModsButton, freeModSelectOverlay));
return baseButtons;
} }
/// <summary> /// <summary>

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Screens; using osu.Framework.Screens;
@ -15,8 +13,8 @@ namespace osu.Game.Screens.OnlinePlay
public virtual string ShortTitle => Title; public virtual string ShortTitle => Title;
[Resolved(CanBeNull = true)] [Resolved]
protected IRoomManager RoomManager { get; private set; } protected IRoomManager? RoomManager { get; private set; }
protected OnlinePlaySubScreen() protected OnlinePlaySubScreen()
{ {

View File

@ -162,17 +162,20 @@ namespace osu.Game.Screens.Play.PlayerSettings
realmWriteTask = realm.WriteAsync(r => realmWriteTask = realm.WriteAsync(r =>
{ {
var settings = r.Find<BeatmapInfo>(beatmap.Value.BeatmapInfo.ID)?.UserSettings; var setInfo = r.Find<BeatmapSetInfo>(beatmap.Value.BeatmapSetInfo.ID);
if (settings == null) // only the case for tests. if (setInfo == null) // only the case for tests.
return; return;
double val = Current.Value; // Apply to all difficulties in a beatmap set for now (they generally always share timing).
foreach (var b in setInfo.Beatmaps)
{
BeatmapUserSettings settings = b.UserSettings;
double val = Current.Value;
if (settings.Offset == val) if (settings.Offset != val)
return; settings.Offset = val;
}
settings.Offset = val;
}); });
} }
} }

View File

@ -17,8 +17,8 @@ namespace osu.Game.Skinning
Anchor = Anchor.TopRight; Anchor = Anchor.TopRight;
Origin = Anchor.TopRight; Origin = Anchor.TopRight;
Scale = new Vector2(0.6f); Scale = new Vector2(0.6f * 0.96f);
Margin = new MarginPadding(10); Margin = new MarginPadding { Vertical = 9, Horizontal = 17 };
} }
protected sealed override OsuSpriteText CreateSpriteText() => new LegacySpriteText(LegacyFont.Score) protected sealed override OsuSpriteText CreateSpriteText() => new LegacySpriteText(LegacyFont.Score)

View File

@ -21,7 +21,7 @@ namespace osu.Game.Skinning
Origin = Anchor.TopRight; Origin = Anchor.TopRight;
Scale = new Vector2(0.96f); Scale = new Vector2(0.96f);
Margin = new MarginPadding(10); Margin = new MarginPadding { Horizontal = 10 };
} }
protected sealed override OsuSpriteText CreateSpriteText() => new LegacySpriteText(LegacyFont.Score) protected sealed override OsuSpriteText CreateSpriteText() => new LegacySpriteText(LegacyFont.Score)

View File

@ -368,7 +368,7 @@ namespace osu.Game.Skinning
{ {
songProgress.Anchor = Anchor.TopRight; songProgress.Anchor = Anchor.TopRight;
songProgress.Origin = Anchor.CentreRight; songProgress.Origin = Anchor.CentreRight;
songProgress.X = -accuracy.ScreenSpaceDeltaToParentSpace(accuracy.ScreenSpaceDrawQuad.Size).X - 10; songProgress.X = -accuracy.ScreenSpaceDeltaToParentSpace(accuracy.ScreenSpaceDrawQuad.Size).X - 18;
songProgress.Y = container.ToLocalSpace(accuracy.ScreenSpaceDrawQuad.TopLeft).Y + (accuracy.ScreenSpaceDeltaToParentSpace(accuracy.ScreenSpaceDrawQuad.Size).Y / 2); songProgress.Y = container.ToLocalSpace(accuracy.ScreenSpaceDrawQuad.TopLeft).Y + (accuracy.ScreenSpaceDeltaToParentSpace(accuracy.ScreenSpaceDrawQuad.Size).Y / 2);
} }
@ -397,8 +397,8 @@ namespace osu.Game.Skinning
new LegacyComboCounter(), new LegacyComboCounter(),
new LegacyScoreCounter(), new LegacyScoreCounter(),
new LegacyAccuracyCounter(), new LegacyAccuracyCounter(),
new LegacyHealthDisplay(),
new LegacySongProgress(), new LegacySongProgress(),
new LegacyHealthDisplay(),
new BarHitErrorMeter(), new BarHitErrorMeter(),
new DefaultKeyCounterDisplay() new DefaultKeyCounterDisplay()
} }

View File

@ -1,4 +1,4 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
#nullable disable #nullable disable
@ -47,7 +47,7 @@ namespace osu.Game.Updater
{ {
Text = $"A newer release of osu! has been found ({version} → {latestTagName}).\n\n" Text = $"A newer release of osu! has been found ({version} → {latestTagName}).\n\n"
+ "Check with your package manager / provider to bring osu! up-to-date!", + "Check with your package manager / provider to bring osu! up-to-date!",
Icon = FontAwesome.Solid.Upload, Icon = FontAwesome.Solid.Download,
}); });
return true; return true;

View File

@ -1,4 +1,4 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
#nullable disable #nullable disable
@ -54,7 +54,7 @@ namespace osu.Game.Updater
{ {
Text = $"A newer release of osu! has been found ({version} → {latestTagName}).\n\n" Text = $"A newer release of osu! has been found ({version} → {latestTagName}).\n\n"
+ "Click here to download the new version, which can be installed over the top of your existing installation", + "Click here to download the new version, which can be installed over the top of your existing installation",
Icon = FontAwesome.Solid.Upload, Icon = FontAwesome.Solid.Download,
Activated = () => Activated = () =>
{ {
host.OpenUrlExternally(getBestUrl(latest)); host.OpenUrlExternally(getBestUrl(latest));

View File

@ -134,7 +134,7 @@ namespace osu.Game.Updater
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Icon = FontAwesome.Solid.Upload, Icon = FontAwesome.Solid.Download,
Size = new Vector2(34), Size = new Vector2(34),
Colour = OsuColour.Gray(0.2f), Colour = OsuColour.Gray(0.2f),
Depth = float.MaxValue, Depth = float.MaxValue,

View File

@ -36,7 +36,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Realm" Version="11.1.2" /> <PackageReference Include="Realm" Version="11.1.2" />
<PackageReference Include="ppy.osu.Framework" Version="2023.716.0" /> <PackageReference Include="ppy.osu.Framework" Version="2023.720.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2023.707.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2023.707.0" />
<PackageReference Include="Sentry" Version="3.28.1" /> <PackageReference Include="Sentry" Version="3.28.1" />
<PackageReference Include="SharpCompress" Version="0.32.2" /> <PackageReference Include="SharpCompress" Version="0.32.2" />

View File

@ -16,6 +16,6 @@
<RuntimeIdentifier>iossimulator-x64</RuntimeIdentifier> <RuntimeIdentifier>iossimulator-x64</RuntimeIdentifier>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Framework.iOS" Version="2023.716.0" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2023.720.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -140,6 +140,8 @@
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ParameterHidesMember/@EntryIndexedValue">HINT</s:String> <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ParameterHidesMember/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ParameterOnlyUsedForPreconditionCheck_002EGlobal/@EntryIndexedValue">HINT</s:String> <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ParameterOnlyUsedForPreconditionCheck_002EGlobal/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ParameterOnlyUsedForPreconditionCheck_002ELocal/@EntryIndexedValue">HINT</s:String> <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ParameterOnlyUsedForPreconditionCheck_002ELocal/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=PartialMethodWithSinglePart/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=PartialTypeWithSinglePart/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=PatternAlwaysOfType/@EntryIndexedValue">DO_NOT_SHOW</s:String> <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=PatternAlwaysOfType/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=PossibleInterfaceMemberAmbiguity/@EntryIndexedValue">HINT</s:String> <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=PossibleInterfaceMemberAmbiguity/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=PossibleMultipleEnumeration/@EntryIndexedValue">HINT</s:String> <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=PossibleMultipleEnumeration/@EntryIndexedValue">HINT</s:String>