1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 13:37:25 +08:00

Merge branch 'master' into menu-star-fountains

This commit is contained in:
Bartłomiej Dach 2023-07-25 21:58:13 +02:00
commit 3b9f250c1b
No known key found for this signature in database
101 changed files with 1570 additions and 509 deletions

View File

@ -10,7 +10,7 @@
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk> <EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.720.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2023.724.0" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>
<!-- Fody does not handle Android build well, and warns when unchanged. <!-- Fody does not handle Android build well, and warns when unchanged.

View File

@ -5,7 +5,7 @@
<key>CFBundleName</key> <key>CFBundleName</key>
<string>osu.Game.Rulesets.Catch.Tests.iOS</string> <string>osu.Game.Rulesets.Catch.Tests.iOS</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>ppy.osu-Game-Rulesets-Catch-Tests-iOS</string> <string>sh.ppy.catch-ruleset-tests</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.0</string> <string>1.0</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
@ -42,4 +42,4 @@
<key>CADisableMinimumFrameDurationOnPhone</key> <key>CADisableMinimumFrameDurationOnPhone</key>
<true/> <true/>
</dict> </dict>
</plist> </plist>

View File

@ -5,7 +5,7 @@
<key>CFBundleName</key> <key>CFBundleName</key>
<string>osu.Game.Rulesets.Mania.Tests.iOS</string> <string>osu.Game.Rulesets.Mania.Tests.iOS</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>ppy.osu-Game-Rulesets-Mania-Tests-iOS</string> <string>sh.ppy.mania-ruleset-tests</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.0</string> <string>1.0</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
@ -42,4 +42,4 @@
<key>CADisableMinimumFrameDurationOnPhone</key> <key>CADisableMinimumFrameDurationOnPhone</key>
<true/> <true/>
</dict> </dict>
</plist> </plist>

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

@ -5,7 +5,7 @@
<key>CFBundleName</key> <key>CFBundleName</key>
<string>osu.Game.Rulesets.Osu.Tests.iOS</string> <string>osu.Game.Rulesets.Osu.Tests.iOS</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>ppy.osu-Game-Rulesets-Osu-Tests-iOS</string> <string>sh.ppy.osu-ruleset-tests</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.0</string> <string>1.0</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
@ -42,4 +42,4 @@
<key>CADisableMinimumFrameDurationOnPhone</key> <key>CADisableMinimumFrameDurationOnPhone</key>
<true/> <true/>
</dict> </dict>
</plist> </plist>

View File

@ -25,6 +25,35 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{ {
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false); protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
[Test]
public void TestSelectAfterFadedOut()
{
var slider = new Slider
{
StartTime = 0,
Position = new Vector2(100, 100),
Path = new SliderPath
{
ControlPoints =
{
new PathControlPoint(),
new PathControlPoint(new Vector2(100))
}
}
};
AddStep("add slider", () => EditorBeatmap.Add(slider));
moveMouseToObject(() => slider);
AddStep("seek after end", () => EditorClock.Seek(750));
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("slider not selected", () => EditorBeatmap.SelectedHitObjects.Count == 0);
AddStep("seek to visible", () => EditorClock.Seek(650));
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddUntilStep("slider selected", () => EditorBeatmap.SelectedHitObjects.Single() == slider);
}
[Test] [Test]
public void TestContextMenuShownCorrectlyForSelectedSlider() public void TestContextMenuShownCorrectlyForSelectedSlider()
{ {

View File

@ -22,7 +22,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints
protected override bool AlwaysShowWhenSelected => true; protected override bool AlwaysShowWhenSelected => true;
protected override bool ShouldBeAlive => base.ShouldBeAlive protected override bool ShouldBeAlive => base.ShouldBeAlive
|| (DrawableObject is not DrawableSpinner && ShowHitMarkers.Value && editorClock.CurrentTime >= Item.StartTime && editorClock.CurrentTime - Item.GetEndTime() < HitCircleOverlapMarker.FADE_OUT_EXTENSION); || (DrawableObject is not DrawableSpinner && ShowHitMarkers.Value && editorClock.CurrentTime >= Item.StartTime
&& editorClock.CurrentTime - Item.GetEndTime() < HitCircleOverlapMarker.FADE_OUT_EXTENSION);
public override bool IsSelectable =>
// Bypass fade out extension from hit markers for selection purposes.
// This is to match stable, where even when the afterimage hit markers are still visible, objects are not selectable.
base.ShouldBeAlive;
protected OsuSelectionBlueprint(T hitObject) protected OsuSelectionBlueprint(T hitObject)
: base(hitObject) : base(hitObject)

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

@ -5,7 +5,7 @@
<key>CFBundleName</key> <key>CFBundleName</key>
<string>osu.Game.Rulesets.Taiko.Tests.iOS</string> <string>osu.Game.Rulesets.Taiko.Tests.iOS</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>ppy.osu-Game-Rulesets-Taiko-Tests-iOS</string> <string>sh.ppy.taiko-ruleset-tests</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.0</string> <string>1.0</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
@ -42,4 +42,4 @@
<key>CADisableMinimumFrameDurationOnPhone</key> <key>CADisableMinimumFrameDurationOnPhone</key>
<true/> <true/>
</dict> </dict>
</plist> </plist>

View File

@ -36,6 +36,28 @@ namespace osu.Game.Rulesets.Taiko.Tests.Judgements
AssertResult<Hit>(0, HitResult.Great); AssertResult<Hit>(0, HitResult.Great);
} }
[Test]
public void TestHitWithBothKeysOnSameFrameDoesNotFallThroughToNextObject()
{
PerformTest(new List<ReplayFrame>
{
new TaikoReplayFrame(0),
new TaikoReplayFrame(1000, TaikoAction.LeftCentre, TaikoAction.RightCentre),
}, CreateBeatmap(new Hit
{
Type = HitType.Centre,
StartTime = 1000,
}, new Hit
{
Type = HitType.Centre,
StartTime = 1020
}));
AssertJudgementCount(2);
AssertResult<Hit>(0, HitResult.Great);
AssertResult<Hit>(1, HitResult.Miss);
}
[Test] [Test]
public void TestHitRimHit() public void TestHitRimHit()
{ {

View File

@ -1,24 +1,71 @@
// 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.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Taiko.Mods; using osu.Game.Rulesets.Taiko.Mods;
using osu.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Rulesets.Taiko.Tests.Mods namespace osu.Game.Rulesets.Taiko.Tests.Mods
{ {
public partial class TestSceneTaikoModHidden : TaikoModTestScene public partial class TestSceneTaikoModHidden : TaikoModTestScene
{ {
private Func<bool> checkAllMaxResultJudgements(int count) => ()
=> Player.ScoreProcessor.JudgedHits >= count
&& Player.Results.All(result => result.Type == result.Judgement.MaxResult);
[Test] [Test]
public void TestDefaultBeatmapTest() => CreateModTest(new ModTestData public void TestDefaultBeatmapTest() => CreateModTest(new ModTestData
{ {
Mod = new TaikoModHidden(), Mod = new TaikoModHidden(),
Autoplay = true, Autoplay = true,
PassCondition = checkSomeAutoplayHits PassCondition = checkAllMaxResultJudgements(4),
}); });
private bool checkSomeAutoplayHits() [Test]
=> Player.ScoreProcessor.JudgedHits >= 4 public void TestHitTwoNotesWithinShortPeriod()
&& Player.Results.All(result => result.Type == result.Judgement.MaxResult); {
const double hit_time = 1;
var beatmap = new Beatmap<TaikoHitObject>
{
HitObjects = new List<TaikoHitObject>
{
new Hit
{
Type = HitType.Rim,
StartTime = hit_time,
},
new Hit
{
Type = HitType.Centre,
StartTime = hit_time * 2,
},
},
BeatmapInfo =
{
Difficulty = new BeatmapDifficulty
{
SliderTickRate = 4,
OverallDifficulty = 0,
},
Ruleset = new TaikoRuleset().RulesetInfo
},
};
beatmap.ControlPointInfo.Add(0, new EffectControlPoint { ScrollSpeed = 0.1f });
CreateModTest(new ModTestData
{
Mod = new TaikoModHidden(),
Autoplay = true,
PassCondition = checkAllMaxResultJudgements(2),
Beatmap = beatmap,
});
}
} }
} }

View File

@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
private bool validActionPressed; private bool validActionPressed;
private bool pressHandledThisFrame; private double? lastPressHandleTime;
private readonly Bindable<HitType> type = new Bindable<HitType>(); private readonly Bindable<HitType> type = new Bindable<HitType>();
@ -76,7 +76,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
HitActions = null; HitActions = null;
HitAction = null; HitAction = null;
validActionPressed = pressHandledThisFrame = false; validActionPressed = false;
lastPressHandleTime = null;
} }
private void updateActionsFromType() private void updateActionsFromType()
@ -114,7 +115,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
public override bool OnPressed(KeyBindingPressEvent<TaikoAction> e) public override bool OnPressed(KeyBindingPressEvent<TaikoAction> e)
{ {
if (pressHandledThisFrame) if (lastPressHandleTime == Time.Current)
return true; return true;
if (Judged) if (Judged)
return false; return false;
@ -128,7 +129,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
// Regardless of whether we've hit or not, any secondary key presses in the same frame should be discarded // Regardless of whether we've hit or not, any secondary key presses in the same frame should be discarded
// E.g. hitting a non-strong centre as a strong should not fall through and perform a hit on the next note // E.g. hitting a non-strong centre as a strong should not fall through and perform a hit on the next note
pressHandledThisFrame = true; lastPressHandleTime = Time.Current;
return result; return result;
} }
@ -139,15 +140,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
base.OnReleased(e); base.OnReleased(e);
} }
protected override void Update()
{
base.Update();
// The input manager processes all input prior to us updating, so this is the perfect time
// for us to remove the extra press blocking, before input is handled in the next frame
pressHandledThisFrame = false;
}
protected override void UpdateHitStateTransforms(ArmedState state) protected override void UpdateHitStateTransforms(ArmedState state)
{ {
Debug.Assert(HitObject.HitWindows != null); Debug.Assert(HitObject.HitWindows != null);

View File

@ -5,7 +5,7 @@
<key>CFBundleName</key> <key>CFBundleName</key>
<string>osu.Game.Tests.iOS</string> <string>osu.Game.Tests.iOS</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>ppy.osu-Game-Tests-iOS</string> <string>sh.ppy.osu-tests</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.0</string> <string>1.0</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
@ -42,4 +42,4 @@
<key>CADisableMinimumFrameDurationOnPhone</key> <key>CADisableMinimumFrameDurationOnPhone</key>
<true/> <true/>
</dict> </dict>
</plist> </plist>

View File

@ -0,0 +1,183 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Checks;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Editing.Checks
{
public class CheckBreaksTest
{
private CheckBreaks check = null!;
[SetUp]
public void Setup()
{
check = new CheckBreaks();
}
[Test]
public void TestBreakTooShort()
{
var beatmap = new Beatmap<HitObject>
{
Breaks = new List<BreakPeriod>
{
new BreakPeriod(0, 649)
}
};
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
var issues = check.Run(context).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckBreaks.IssueTemplateTooShort);
}
[Test]
public void TestBreakStartsEarly()
{
var beatmap = new Beatmap<HitObject>
{
HitObjects =
{
new HitCircle { StartTime = 0 },
new HitCircle { StartTime = 1_200 }
},
Breaks = new List<BreakPeriod>
{
new BreakPeriod(100, 751)
}
};
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
var issues = check.Run(context).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckBreaks.IssueTemplateEarlyStart);
}
[Test]
public void TestBreakEndsLate()
{
var beatmap = new Beatmap<HitObject>
{
HitObjects =
{
new HitCircle { StartTime = 0 },
new HitCircle { StartTime = 1_298 }
},
Breaks = new List<BreakPeriod>
{
new BreakPeriod(200, 850)
}
};
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
var issues = check.Run(context).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckBreaks.IssueTemplateLateEnd);
}
[Test]
public void TestBreakAfterLastObjectStartsEarly()
{
var beatmap = new Beatmap<HitObject>
{
HitObjects =
{
new HitCircle { StartTime = 0 },
new HitCircle { StartTime = 1200 }
},
Breaks = new List<BreakPeriod>
{
new BreakPeriod(1398, 2300)
}
};
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
var issues = check.Run(context).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckBreaks.IssueTemplateEarlyStart);
}
[Test]
public void TestBreakBeforeFirstObjectEndsLate()
{
var beatmap = new Beatmap<HitObject>
{
HitObjects =
{
new HitCircle { StartTime = 1100 },
new HitCircle { StartTime = 1500 }
},
Breaks = new List<BreakPeriod>
{
new BreakPeriod(0, 652)
}
};
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
var issues = check.Run(context).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckBreaks.IssueTemplateLateEnd);
}
[Test]
public void TestBreakMultipleObjectsEarly()
{
var beatmap = new Beatmap<HitObject>
{
HitObjects =
{
new HitCircle { StartTime = 0 },
new HitCircle { StartTime = 1_297 },
new HitCircle { StartTime = 1_298 }
},
Breaks = new List<BreakPeriod>
{
new BreakPeriod(200, 850)
}
};
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
var issues = check.Run(context).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckBreaks.IssueTemplateLateEnd);
}
[Test]
public void TestBreaksCorrect()
{
var beatmap = new Beatmap<HitObject>
{
HitObjects =
{
new HitCircle { StartTime = 0 },
new HitCircle { StartTime = 1_300 }
},
Breaks = new List<BreakPeriod>
{
new BreakPeriod(200, 850)
}
};
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
var issues = check.Run(context).ToList();
Assert.That(issues, Is.Empty);
}
}
}

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

@ -340,14 +340,14 @@ namespace osu.Game.Tests.Visual.Editing
public void TestCyclicSelectionBackwards() public void TestCyclicSelectionBackwards()
{ {
var firstObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 0 }; var firstObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 0 };
var secondObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 300 }; var secondObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 200 };
var thirdObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 600 }; var thirdObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 400 };
AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[] { firstObject, secondObject, thirdObject })); AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[] { firstObject, secondObject, thirdObject }));
moveMouseToObject(() => firstObject); moveMouseToObject(() => firstObject);
AddStep("seek to third", () => EditorClock.Seek(600)); AddStep("seek to third", () => EditorClock.Seek(350));
AddStep("left click", () => InputManager.Click(MouseButton.Left)); AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("third selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(thirdObject)); AddAssert("third selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(thirdObject));

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

@ -1,8 +1,11 @@
// 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.Linq;
using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Tournament.Models; using osu.Game.Tournament.Models;
using osu.Game.Tournament.Screens.Ladder.Components; using osu.Game.Tournament.Screens.Ladder.Components;
@ -10,60 +13,67 @@ namespace osu.Game.Tournament.Tests.Components
{ {
public partial class TestSceneDrawableTournamentMatch : TournamentTestScene public partial class TestSceneDrawableTournamentMatch : TournamentTestScene
{ {
public TestSceneDrawableTournamentMatch() [Test]
public void TestBasic()
{ {
Container<DrawableTournamentMatch> level1; Container<DrawableTournamentMatch> level1 = null!;
Container<DrawableTournamentMatch> level2; Container<DrawableTournamentMatch> level2 = null!;
var match1 = new TournamentMatch( TournamentMatch match1 = null!;
new TournamentTeam { FlagName = { Value = "AU" }, FullName = { Value = "Australia" }, }, TournamentMatch match2 = null!;
new TournamentTeam { FlagName = { Value = "JP" }, FullName = { Value = "Japan" }, Acronym = { Value = "JPN" } })
AddStep("setup test", () =>
{ {
Team1Score = { Value = 4 }, match1 = new TournamentMatch(
Team2Score = { Value = 1 }, new TournamentTeam { FlagName = { Value = "AU" }, FullName = { Value = "Australia" }, },
}; new TournamentTeam { FlagName = { Value = "JP" }, FullName = { Value = "Japan" }, Acronym = { Value = "JPN" } })
var match2 = new TournamentMatch(
new TournamentTeam
{ {
FlagName = { Value = "RO" }, Team1Score = { Value = 4 },
FullName = { Value = "Romania" }, Team2Score = { Value = 1 },
} };
);
Child = new FillFlowContainer match2 = new TournamentMatch(
{ new TournamentTeam
RelativeSizeAxes = Axes.Both, {
Direction = FillDirection.Horizontal, FlagName = { Value = "RO" },
Children = new Drawable[] FullName = { Value = "Romania" },
}
);
Child = new FillFlowContainer
{ {
level1 = new FillFlowContainer<DrawableTournamentMatch> RelativeSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{ {
AutoSizeAxes = Axes.X, level1 = new FillFlowContainer<DrawableTournamentMatch>
Direction = FillDirection.Vertical,
Children = new[]
{ {
new DrawableTournamentMatch(match1), AutoSizeAxes = Axes.X,
new DrawableTournamentMatch(match2), Direction = FillDirection.Vertical,
new DrawableTournamentMatch(new TournamentMatch()), Children = new[]
} {
}, new DrawableTournamentMatch(match1),
level2 = new FillFlowContainer<DrawableTournamentMatch> new DrawableTournamentMatch(match2),
{ new DrawableTournamentMatch(new TournamentMatch()),
AutoSizeAxes = Axes.X, }
Direction = FillDirection.Vertical, },
Margin = new MarginPadding(20), level2 = new FillFlowContainer<DrawableTournamentMatch>
Children = new[]
{ {
new DrawableTournamentMatch(new TournamentMatch()), AutoSizeAxes = Axes.X,
new DrawableTournamentMatch(new TournamentMatch()) Direction = FillDirection.Vertical,
Margin = new MarginPadding(20),
Children = new[]
{
new DrawableTournamentMatch(new TournamentMatch()),
new DrawableTournamentMatch(new TournamentMatch())
}
} }
} }
} };
};
level1.Children[0].Match.Progression.Value = level2.Children[0].Match; level1.Children[0].Match.Progression.Value = level2.Children[0].Match;
level1.Children[1].Match.Progression.Value = level2.Children[0].Match; level1.Children[1].Match.Progression.Value = level2.Children[0].Match;
});
AddRepeatStep("change scores", () => match1.Team2Score.Value++, 4); AddRepeatStep("change scores", () => match1.Team2Score.Value++, 4);
AddStep("add new team", () => match2.Team2.Value = new TournamentTeam { FlagName = { Value = "PT" }, FullName = { Value = "Portugal" } }); AddStep("add new team", () => match2.Team2.Value = new TournamentTeam { FlagName = { Value = "PT" }, FullName = { Value = "Portugal" } });
@ -78,6 +88,9 @@ namespace osu.Game.Tournament.Tests.Components
AddRepeatStep("change scores", () => level2.Children[0].Match.Team1Score.Value++, 5); AddRepeatStep("change scores", () => level2.Children[0].Match.Team1Score.Value++, 5);
AddRepeatStep("change scores", () => level2.Children[0].Match.Team2Score.Value++, 4); AddRepeatStep("change scores", () => level2.Children[0].Match.Team2Score.Value++, 4);
AddStep("select as current", () => match1.Current.Value = true);
AddStep("select as editing", () => this.ChildrenOfType<DrawableTournamentMatch>().Last().Selected = true);
} }
} }
} }

View File

@ -10,7 +10,7 @@ using osu.Game.Tournament.Screens.Drawings;
namespace osu.Game.Tournament.Tests.Screens namespace osu.Game.Tournament.Tests.Screens
{ {
public partial class TestSceneDrawingsScreen : TournamentTestScene public partial class TestSceneDrawingsScreen : TournamentScreenTestScene
{ {
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(Storage storage) private void load(Storage storage)

View File

@ -15,11 +15,25 @@ using osu.Game.Tournament.Screens.Gameplay.Components;
namespace osu.Game.Tournament.Tests.Screens namespace osu.Game.Tournament.Tests.Screens
{ {
public partial class TestSceneGameplayScreen : TournamentTestScene public partial class TestSceneGameplayScreen : TournamentScreenTestScene
{ {
[Cached] [Cached]
private TournamentMatchChatDisplay chat = new TournamentMatchChatDisplay { Width = 0.5f }; private TournamentMatchChatDisplay chat = new TournamentMatchChatDisplay { Width = 0.5f };
[Test]
public void TestWarmup()
{
createScreen();
checkScoreVisibility(false);
toggleWarmup();
checkScoreVisibility(true);
toggleWarmup();
checkScoreVisibility(false);
}
[Test] [Test]
public void TestStartupState([Values] TourneyState state) public void TestStartupState([Values] TourneyState state)
{ {
@ -35,20 +49,6 @@ namespace osu.Game.Tournament.Tests.Screens
createScreen(); createScreen();
} }
[Test]
public void TestWarmup()
{
createScreen();
checkScoreVisibility(false);
toggleWarmup();
checkScoreVisibility(true);
toggleWarmup();
checkScoreVisibility(false);
}
private void createScreen() private void createScreen()
{ {
AddStep("setup screen", () => AddStep("setup screen", () =>

View File

@ -1,23 +1,100 @@
// 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 System;
using System.Linq;
using Newtonsoft.Json;
using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Graphics.Cursor; using osu.Game.Graphics.Cursor;
using osu.Game.Tournament.Screens.Editors; using osu.Game.Tournament.Screens.Editors;
using osu.Framework.Testing;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Dialog;
using osu.Game.Tournament.Screens.Editors.Components;
using osuTK;
using osuTK.Input;
namespace osu.Game.Tournament.Tests.Screens namespace osu.Game.Tournament.Tests.Screens
{ {
public partial class TestSceneLadderEditorScreen : TournamentTestScene public partial class TestSceneLadderEditorScreen : TournamentScreenTestScene
{ {
[BackgroundDependencyLoader] private LadderEditorScreen ladderEditorScreen = null!;
private void load() private OsuContextMenuContainer? osuContextMenuContainer;
[SetUp]
public void Setup() => Schedule(() =>
{ {
Add(new OsuContextMenuContainer ladderEditorScreen = new LadderEditorScreen();
Add(osuContextMenuContainer = new OsuContextMenuContainer
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Child = new LadderEditorScreen() Child = ladderEditorScreen = new LadderEditorScreen()
}); });
});
[Test]
public void TestResetBracketTeamsCancelled()
{
Bindable<string> matchBeforeReset = new Bindable<string>();
AddStep("save current match state", () =>
{
matchBeforeReset.Value = JsonConvert.SerializeObject(Ladder.CurrentMatch.Value);
});
AddStep("pull up context menu", () =>
{
InputManager.MoveMouseTo(ladderEditorScreen);
InputManager.Click(MouseButton.Right);
});
AddStep("click Reset teams button", () =>
{
InputManager.MoveMouseTo(osuContextMenuContainer.ChildrenOfType<DrawableOsuMenuItem>().Last(p =>
((OsuMenuItem)p.Item).Type == MenuItemType.Destructive), new Vector2(5, 0));
InputManager.Click(MouseButton.Left);
});
AddAssert("dialog displayed", () => DialogOverlay.CurrentDialog is LadderResetTeamsDialog);
AddStep("click cancel", () =>
{
InputManager.MoveMouseTo(DialogOverlay.CurrentDialog.ChildrenOfType<PopupDialogButton>().Last());
InputManager.Click(MouseButton.Left);
});
AddUntilStep("dialog dismissed", () => DialogOverlay.CurrentDialog is not LadderResetTeamsDialog);
AddAssert("assert ladder teams unchanged", () => string.Equals(matchBeforeReset.Value, JsonConvert.SerializeObject(Ladder.CurrentMatch.Value), StringComparison.Ordinal));
}
[Test]
public void TestResetBracketTeams()
{
AddStep("pull up context menu", () =>
{
InputManager.MoveMouseTo(ladderEditorScreen);
InputManager.Click(MouseButton.Right);
});
AddStep("click Reset teams button", () =>
{
InputManager.MoveMouseTo(osuContextMenuContainer.ChildrenOfType<DrawableOsuMenuItem>().Last(p =>
((OsuMenuItem)p.Item).Type == MenuItemType.Destructive), new Vector2(5, 0));
InputManager.Click(MouseButton.Left);
});
AddAssert("dialog displayed", () => DialogOverlay.CurrentDialog is LadderResetTeamsDialog);
AddStep("click confirmation", () =>
{
InputManager.MoveMouseTo(DialogOverlay.CurrentDialog.ChildrenOfType<PopupDialogButton>().First());
InputManager.PressButton(MouseButton.Left);
});
AddUntilStep("dialog dismissed", () => DialogOverlay.CurrentDialog is not LadderResetTeamsDialog);
AddStep("release mouse button", () => InputManager.ReleaseButton(MouseButton.Left));
AddAssert("assert ladder teams reset", () => Ladder.CurrentMatch.Value.Team1.Value == null && Ladder.CurrentMatch.Value.Team2.Value == null);
} }
} }
} }

View File

@ -8,7 +8,7 @@ using osu.Game.Tournament.Screens.Ladder;
namespace osu.Game.Tournament.Tests.Screens namespace osu.Game.Tournament.Tests.Screens
{ {
public partial class TestSceneLadderScreen : TournamentTestScene public partial class TestSceneLadderScreen : TournamentScreenTestScene
{ {
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()

View File

@ -14,7 +14,7 @@ using osu.Game.Tournament.Screens.MapPool;
namespace osu.Game.Tournament.Tests.Screens namespace osu.Game.Tournament.Tests.Screens
{ {
public partial class TestSceneMapPoolScreen : TournamentTestScene public partial class TestSceneMapPoolScreen : TournamentScreenTestScene
{ {
private MapPoolScreen screen; private MapPoolScreen screen;

View File

@ -1,13 +1,15 @@
// 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.Game.Tournament.Screens.Editors; using osu.Game.Tournament.Screens.Editors;
namespace osu.Game.Tournament.Tests.Screens namespace osu.Game.Tournament.Tests.Screens
{ {
public partial class TestSceneRoundEditorScreen : TournamentTestScene public partial class TestSceneRoundEditorScreen : TournamentScreenTestScene
{ {
public TestSceneRoundEditorScreen() [BackgroundDependencyLoader]
private void load()
{ {
Add(new RoundEditorScreen Add(new RoundEditorScreen
{ {

View File

@ -12,7 +12,7 @@ using osu.Game.Tournament.Screens.Schedule;
namespace osu.Game.Tournament.Tests.Screens namespace osu.Game.Tournament.Tests.Screens
{ {
public partial class TestSceneScheduleScreen : TournamentTestScene public partial class TestSceneScheduleScreen : TournamentScreenTestScene
{ {
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()

View File

@ -7,12 +7,13 @@ using osu.Game.Tournament.Screens.Editors;
namespace osu.Game.Tournament.Tests.Screens namespace osu.Game.Tournament.Tests.Screens
{ {
public partial class TestSceneSeedingEditorScreen : TournamentTestScene public partial class TestSceneSeedingEditorScreen : TournamentScreenTestScene
{ {
[Cached] [Cached]
private readonly LadderInfo ladder = new LadderInfo(); private readonly LadderInfo ladder = new LadderInfo();
public TestSceneSeedingEditorScreen() [BackgroundDependencyLoader]
private void load()
{ {
var match = CreateSampleMatch(); var match = CreateSampleMatch();

View File

@ -12,7 +12,7 @@ using osu.Game.Tournament.Screens.TeamIntro;
namespace osu.Game.Tournament.Tests.Screens namespace osu.Game.Tournament.Tests.Screens
{ {
public partial class TestSceneSeedingScreen : TournamentTestScene public partial class TestSceneSeedingScreen : TournamentScreenTestScene
{ {
[Cached] [Cached]
private readonly LadderInfo ladder = new LadderInfo private readonly LadderInfo ladder = new LadderInfo

View File

@ -6,7 +6,7 @@ using osu.Game.Tournament.Screens.Setup;
namespace osu.Game.Tournament.Tests.Screens namespace osu.Game.Tournament.Tests.Screens
{ {
public partial class TestSceneSetupScreen : TournamentTestScene public partial class TestSceneSetupScreen : TournamentScreenTestScene
{ {
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()

View File

@ -6,7 +6,7 @@ using osu.Game.Tournament.Screens.Showcase;
namespace osu.Game.Tournament.Tests.Screens namespace osu.Game.Tournament.Tests.Screens
{ {
public partial class TestSceneShowcaseScreen : TournamentTestScene public partial class TestSceneShowcaseScreen : TournamentScreenTestScene
{ {
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()

View File

@ -5,7 +5,7 @@ using osu.Game.Tournament.Screens.Setup;
namespace osu.Game.Tournament.Tests.Screens namespace osu.Game.Tournament.Tests.Screens
{ {
public partial class TestSceneStablePathSelectScreen : TournamentTestScene public partial class TestSceneStablePathSelectScreen : TournamentScreenTestScene
{ {
public TestSceneStablePathSelectScreen() public TestSceneStablePathSelectScreen()
{ {

View File

@ -1,13 +1,15 @@
// 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.Game.Tournament.Screens.Editors; using osu.Game.Tournament.Screens.Editors;
namespace osu.Game.Tournament.Tests.Screens namespace osu.Game.Tournament.Tests.Screens
{ {
public partial class TestSceneTeamEditorScreen : TournamentTestScene public partial class TestSceneTeamEditorScreen : TournamentScreenTestScene
{ {
public TestSceneTeamEditorScreen() [BackgroundDependencyLoader]
private void load()
{ {
Add(new TeamEditorScreen Add(new TeamEditorScreen
{ {

View File

@ -9,7 +9,7 @@ using osu.Game.Tournament.Screens.TeamIntro;
namespace osu.Game.Tournament.Tests.Screens namespace osu.Game.Tournament.Tests.Screens
{ {
public partial class TestSceneTeamIntroScreen : TournamentTestScene public partial class TestSceneTeamIntroScreen : TournamentScreenTestScene
{ {
[Cached] [Cached]
private readonly LadderInfo ladder = new LadderInfo(); private readonly LadderInfo ladder = new LadderInfo();

View File

@ -8,7 +8,7 @@ using osu.Game.Tournament.Screens.TeamWin;
namespace osu.Game.Tournament.Tests.Screens namespace osu.Game.Tournament.Tests.Screens
{ {
public partial class TestSceneTeamWinScreen : TournamentTestScene public partial class TestSceneTeamWinScreen : TournamentScreenTestScene
{ {
[Test] [Test]
public void TestBasic() public void TestBasic()

View File

@ -0,0 +1,38 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osuTK;
namespace osu.Game.Tournament.Tests
{
public abstract partial class TournamentScreenTestScene : TournamentTestScene
{
protected override Container<Drawable> Content { get; } = new TournamentScalingContainer();
[BackgroundDependencyLoader]
private void load()
{
base.Content.Add(Content);
}
private partial class TournamentScalingContainer : DrawSizePreservingFillContainer
{
public TournamentScalingContainer()
{
TargetDrawSize = new Vector2(1920, 1080);
RelativeSizeAxes = Axes.Both;
}
protected override void Update()
{
base.Update();
Scale = new Vector2(Math.Min(1, Content.DrawWidth / (1920 + TournamentSceneManager.CONTROL_AREA_WIDTH)));
}
}
}
}

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,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.Linq; using System.Linq;
using System.Threading; using System.Threading;
using osu.Framework.Allocation; using osu.Framework.Allocation;
@ -10,6 +8,7 @@ using osu.Framework.Platform;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Overlays;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
using osu.Game.Tournament.IO; using osu.Game.Tournament.IO;
@ -18,19 +17,22 @@ using osu.Game.Tournament.Models;
namespace osu.Game.Tournament.Tests namespace osu.Game.Tournament.Tests
{ {
public abstract partial class TournamentTestScene : OsuTestScene public abstract partial class TournamentTestScene : OsuManualInputManagerTestScene
{ {
private TournamentMatch match; [Cached(typeof(IDialogOverlay))]
protected readonly DialogOverlay DialogOverlay = new DialogOverlay { Depth = float.MinValue };
[Cached] [Cached]
protected LadderInfo Ladder { get; private set; } = new LadderInfo(); protected LadderInfo Ladder { get; private set; } = new LadderInfo();
[Resolved]
private RulesetStore rulesetStore { get; set; }
[Cached] [Cached]
protected MatchIPCInfo IPCInfo { get; private set; } = new MatchIPCInfo(); protected MatchIPCInfo IPCInfo { get; private set; } = new MatchIPCInfo();
[Resolved]
private RulesetStore rulesetStore { get; set; } = null!;
private TournamentMatch match = null!;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(TournamentStorage storage) private void load(TournamentStorage storage)
{ {
@ -45,6 +47,8 @@ namespace osu.Game.Tournament.Tests
Ruleset.BindTo(Ladder.Ruleset); Ruleset.BindTo(Ladder.Ruleset);
Dependencies.CacheAs(new StableInfo(storage)); Dependencies.CacheAs(new StableInfo(storage));
Add(DialogOverlay);
} }
[SetUpSteps] [SetUpSteps]
@ -167,7 +171,7 @@ namespace osu.Game.Tournament.Tests
public partial class TournamentTestSceneTestRunner : TournamentGameBase, ITestSceneTestRunner public partial class TournamentTestSceneTestRunner : TournamentGameBase, ITestSceneTestRunner
{ {
private TestSceneTestRunner.TestRunner runner; private TestSceneTestRunner.TestRunner runner = null!;
protected override void LoadAsyncComplete() protected override void LoadAsyncComplete()
{ {

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.Bindables; using osu.Framework.Bindables;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
@ -12,24 +10,21 @@ namespace osu.Game.Tournament.Components
{ {
public partial class DateTextBox : SettingsTextBox public partial class DateTextBox : SettingsTextBox
{ {
public new Bindable<DateTimeOffset> Current private readonly BindableWithCurrent<DateTimeOffset> current = new BindableWithCurrent<DateTimeOffset>();
public new Bindable<DateTimeOffset>? Current
{ {
get => current; get => current;
set set => current.Current = value;
{
current = value.GetBoundCopy();
current.BindValueChanged(dto =>
base.Current.Value = dto.NewValue.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"), true);
}
} }
// hold a reference to the provided bindable so we don't have to in every settings section.
private Bindable<DateTimeOffset> current = new Bindable<DateTimeOffset>();
public DateTextBox() public DateTextBox()
{ {
base.Current = new Bindable<string>(string.Empty); base.Current = new Bindable<string>(string.Empty);
current.BindValueChanged(dto =>
base.Current.Value = dto.NewValue.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"), true);
((OsuTextBox)Control).OnCommit += (sender, _) => ((OsuTextBox)Control).OnCommit += (sender, _) =>
{ {
try try

View File

@ -56,7 +56,7 @@ namespace osu.Game.Tournament.Components
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = Color4.Black, Colour = Color4.Black,
}, },
new UpdateableOnlineBeatmapSetCover new NoUnloadBeatmapSetCover
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(0.5f), Colour = OsuColour.Gray(0.5f),
@ -180,5 +180,15 @@ namespace osu.Game.Tournament.Components
Alpha = 1; Alpha = 1;
} }
} }
private partial class NoUnloadBeatmapSetCover : UpdateableOnlineBeatmapSetCover
{
// As covers are displayed on stream, we want them to load as soon as possible.
protected override double LoadDelay => 0;
// Use DelayedLoadWrapper to avoid content unloading when switching away to another screen.
protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Func<Drawable> createContentFunc, double timeBeforeLoad)
=> new DelayedLoadWrapper(createContentFunc, timeBeforeLoad);
}
} }
} }

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

@ -0,0 +1,20 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Graphics.Sprites;
using osu.Game.Overlays.Dialog;
using osu.Game.Tournament.Models;
namespace osu.Game.Tournament.Screens.Editors.Components
{
public partial class DeleteRoundDialog : DangerousActionDialog
{
public DeleteRoundDialog(TournamentRound round, Action action)
{
HeaderText = round.Name.Value.Length > 0 ? $@"Delete round ""{round.Name.Value}""?" : @"Delete unnamed round?";
Icon = FontAwesome.Solid.Trash;
DangerousAction = action;
}
}
}

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 System;
using osu.Framework.Graphics.Sprites;
using osu.Game.Overlays.Dialog;
using osu.Game.Tournament.Models;
namespace osu.Game.Tournament.Screens.Editors.Components
{
public partial class DeleteTeamDialog : DangerousActionDialog
{
public DeleteTeamDialog(TournamentTeam team, Action action)
{
HeaderText = team.FullName.Value.Length > 0 ? $@"Delete team ""{team.FullName.Value}""?" :
team.Acronym.Value.Length > 0 ? $@"Delete team ""{team.Acronym.Value}""?" :
@"Delete unnamed team?";
Icon = FontAwesome.Solid.Trash;
DangerousAction = action;
}
}
}

View File

@ -0,0 +1,19 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Graphics.Sprites;
using osu.Game.Overlays.Dialog;
namespace osu.Game.Tournament.Screens.Editors.Components
{
public partial class LadderResetTeamsDialog : DangerousActionDialog
{
public LadderResetTeamsDialog(Action action)
{
HeaderText = @"Reset teams?";
Icon = FontAwesome.Solid.Undo;
DangerousAction = action;
}
}
}

View File

@ -0,0 +1,19 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Graphics.Sprites;
using osu.Game.Overlays.Dialog;
namespace osu.Game.Tournament.Screens.Editors.Components
{
public partial class TournamentClearAllDialog : DangerousActionDialog
{
public TournamentClearAllDialog(Action action)
{
HeaderText = @"Clear all?";
Icon = FontAwesome.Solid.Trash;
DangerousAction = action;
}
}
}

View File

@ -6,6 +6,7 @@
using System; using System;
using System.Drawing; using System.Drawing;
using System.Linq; using System.Linq;
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;
@ -14,8 +15,11 @@ 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.Screens.Edit.Compose.Components;
using osu.Game.Tournament.Components; using osu.Game.Tournament.Components;
using osu.Game.Overlays;
using osu.Game.Tournament.Models; using osu.Game.Tournament.Models;
using osu.Game.Tournament.Screens.Editors.Components;
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;
using osuTK; using osuTK;
@ -26,11 +30,17 @@ namespace osu.Game.Tournament.Screens.Editors
[Cached] [Cached]
public partial class LadderEditorScreen : LadderScreen, IHasContextMenu public partial class LadderEditorScreen : LadderScreen, IHasContextMenu
{ {
public const float GRID_SPACING = 10;
[Cached] [Cached]
private LadderEditorInfo editorInfo = new LadderEditorInfo(); private LadderEditorInfo editorInfo = new LadderEditorInfo();
private WarningBox rightClickMessage; private WarningBox rightClickMessage;
[Resolved(canBeNull: true)]
[CanBeNull]
private IDialogOverlay dialogOverlay { get; set; }
protected override bool DrawLoserPaths => true; protected override bool DrawLoserPaths => true;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -43,6 +53,13 @@ namespace osu.Game.Tournament.Screens.Editors
AddInternal(rightClickMessage = new WarningBox("Right click to place and link matches")); AddInternal(rightClickMessage = new WarningBox("Right click to place and link matches"));
ScrollContent.Add(new RectangularPositionSnapGrid(Vector2.Zero)
{
Spacing = new Vector2(GRID_SPACING),
RelativeSizeAxes = Axes.Both,
Depth = float.MaxValue
});
LadderInfo.Matches.CollectionChanged += (_, _) => updateMessage(); LadderInfo.Matches.CollectionChanged += (_, _) => updateMessage();
updateMessage(); updateMessage();
} }
@ -68,13 +85,20 @@ namespace osu.Game.Tournament.Screens.Editors
{ {
new OsuMenuItem("Create new match", MenuItemType.Highlighted, () => new OsuMenuItem("Create new match", MenuItemType.Highlighted, () =>
{ {
var pos = MatchesContainer.ToLocalSpace(GetContainingInputManager().CurrentState.Mouse.Position); Vector2 pos = MatchesContainer.ToLocalSpace(GetContainingInputManager().CurrentState.Mouse.Position);
LadderInfo.Matches.Add(new TournamentMatch { Position = { Value = new Point((int)pos.X, (int)pos.Y) } }); TournamentMatch newMatch = new TournamentMatch { Position = { Value = new Point((int)pos.X, (int)pos.Y) } };
LadderInfo.Matches.Add(newMatch);
editorInfo.Selected.Value = newMatch;
}), }),
new OsuMenuItem("Reset teams", MenuItemType.Destructive, () => new OsuMenuItem("Reset teams", MenuItemType.Destructive, () =>
{ {
foreach (var p in MatchesContainer) dialogOverlay?.Push(new LadderResetTeamsDialog(() =>
p.Match.Reset(); {
foreach (var p in MatchesContainer)
p.Match.Reset();
}));
}) })
}; };
} }

View File

@ -11,9 +11,11 @@ using osu.Game.Graphics;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings;
using osu.Game.Tournament.Components; using osu.Game.Tournament.Components;
using osu.Game.Tournament.Models; using osu.Game.Tournament.Models;
using osu.Game.Tournament.Screens.Editors.Components;
using osuTK; using osuTK;
namespace osu.Game.Tournament.Screens.Editors namespace osu.Game.Tournament.Screens.Editors
@ -29,6 +31,9 @@ namespace osu.Game.Tournament.Screens.Editors
[Resolved] [Resolved]
private LadderInfo ladderInfo { get; set; } = null!; private LadderInfo ladderInfo { get; set; } = null!;
[Resolved]
private IDialogOverlay? dialogOverlay { get; set; }
public RoundRow(TournamentRound round) public RoundRow(TournamentRound round)
{ {
Model = round; Model = round;
@ -99,11 +104,11 @@ namespace osu.Game.Tournament.Screens.Editors
RelativeSizeAxes = Axes.None, RelativeSizeAxes = Axes.None,
Width = 150, Width = 150,
Text = "Delete Round", Text = "Delete Round",
Action = () => Action = () => dialogOverlay?.Push(new DeleteRoundDialog(Model, () =>
{ {
Expire(); Expire();
ladderInfo.Rounds.Remove(Model); ladderInfo.Rounds.Remove(Model);
}, }))
} }
}; };

View File

@ -11,10 +11,11 @@ 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;
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.Editors.Components;
using osu.Game.Tournament.Screens.Drawings.Components;
using osu.Game.Users; using osu.Game.Users;
using osuTK; using osuTK;
@ -59,11 +60,12 @@ 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; }
[Resolved]
private IDialogOverlay? dialogOverlay { get; set; }
[Resolved] [Resolved]
private LadderInfo ladderInfo { get; set; } = null!; private LadderInfo ladderInfo { get; set; } = null!;
@ -74,10 +76,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 +88,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 +135,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 +144,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 = () => dialogOverlay?.Push(new DeleteTeamDialog(Model, () =>
{
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 +192,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 +209,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 +236,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 +250,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 +296,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,8 +13,9 @@ 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.Overlays;
using osu.Game.Tournament.Components; using osu.Game.Tournament.Components;
using osu.Game.Tournament.Screens.Editors.Components;
using osuTK; using osuTK;
namespace osu.Game.Tournament.Screens.Editors namespace osu.Game.Tournament.Screens.Editors
@ -27,23 +26,27 @@ namespace osu.Game.Tournament.Screens.Editors
{ {
protected abstract BindableList<TModel> Storage { get; } protected abstract BindableList<TModel> Storage { get; }
private FillFlowContainer<TDrawable> flow; [Resolved]
private IDialogOverlay? dialogOverlay { get; set; }
[Resolved(canBeNull: true)] private FillFlowContainer<TDrawable> flow = null!;
private TournamentSceneManager sceneManager { get; set; }
protected ControlPanel ControlPanel; [Resolved]
private TournamentSceneManager? sceneManager { get; set; }
private readonly TournamentScreen parentScreen; protected ControlPanel ControlPanel = null!;
private BackButton backButton;
protected TournamentEditorScreen(TournamentScreen parentScreen = null) private readonly TournamentScreen? parentScreen;
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 +66,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,11 +79,15 @@ 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 = () =>
{
dialogOverlay?.Push(new TournamentClearAllDialog(() => Storage.Clear()));
}
}, },
} }
} }

View File

@ -28,6 +28,8 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
private readonly MatchScoreCounter score1Text; private readonly MatchScoreCounter score1Text;
private readonly MatchScoreCounter score2Text; private readonly MatchScoreCounter score2Text;
private readonly MatchScoreDiffCounter scoreDiffText;
private readonly Drawable score1Bar; private readonly Drawable score1Bar;
private readonly Drawable score2Bar; private readonly Drawable score2Bar;
@ -88,6 +90,16 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre Origin = Anchor.TopCentre
}, },
scoreDiffText = new MatchScoreDiffCounter
{
Anchor = Anchor.TopCentre,
Margin = new MarginPadding
{
Top = bar_height / 4,
Horizontal = 8
},
Alpha = 0
}
}; };
} }
@ -119,6 +131,10 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
losingBar.ResizeWidthTo(0, 400, Easing.OutQuint); losingBar.ResizeWidthTo(0, 400, Easing.OutQuint);
winningBar.ResizeWidthTo(Math.Min(0.4f, MathF.Pow(diff / 1500000f, 0.5f) / 2), 400, Easing.OutQuint); winningBar.ResizeWidthTo(Math.Min(0.4f, MathF.Pow(diff / 1500000f, 0.5f) / 2), 400, Easing.OutQuint);
scoreDiffText.Alpha = diff != 0 ? 1 : 0;
scoreDiffText.Current.Value = -diff;
scoreDiffText.Origin = score1.Value > score2.Value ? Anchor.TopLeft : Anchor.TopRight;
} }
protected override void UpdateAfterChildren() protected override void UpdateAfterChildren()
@ -154,5 +170,14 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
? OsuFont.Torus.With(weight: FontWeight.Bold, size: 50, fixedWidth: true) ? OsuFont.Torus.With(weight: FontWeight.Bold, size: 50, fixedWidth: true)
: OsuFont.Torus.With(weight: FontWeight.Regular, size: 40, fixedWidth: true); : OsuFont.Torus.With(weight: FontWeight.Regular, size: 40, fixedWidth: true);
} }
private partial class MatchScoreDiffCounter : CommaSeparatedScoreCounter
{
protected override OsuSpriteText CreateSpriteText() => base.CreateSpriteText().With(s =>
{
s.Spacing = new Vector2(-2);
s.Font = OsuFont.Torus.With(weight: FontWeight.Regular, size: bar_height, fixedWidth: true);
});
}
} }
} }

View File

@ -139,7 +139,7 @@ namespace osu.Game.Tournament.Screens.Gameplay
base.LoadComplete(); base.LoadComplete();
State.BindTo(ipc.State); State.BindTo(ipc.State);
State.BindValueChanged(stateChanged, true); State.BindValueChanged(_ => updateState(), true);
} }
protected override void CurrentMatchChanged(ValueChangedEvent<TournamentMatch> match) protected override void CurrentMatchChanged(ValueChangedEvent<TournamentMatch> match)
@ -150,20 +150,47 @@ namespace osu.Game.Tournament.Screens.Gameplay
return; return;
warmup.Value = match.NewValue.Team1Score.Value + match.NewValue.Team2Score.Value == 0; warmup.Value = match.NewValue.Team1Score.Value + match.NewValue.Team2Score.Value == 0;
scheduledOperation?.Cancel(); scheduledScreenChange?.Cancel();
} }
private ScheduledDelegate scheduledOperation; private ScheduledDelegate scheduledScreenChange;
private ScheduledDelegate scheduledContract;
private TournamentMatchScoreDisplay scoreDisplay; private TournamentMatchScoreDisplay scoreDisplay;
private TourneyState lastState; private TourneyState lastState;
private MatchHeader header; private MatchHeader header;
private void stateChanged(ValueChangedEvent<TourneyState> state) private void contract()
{
scheduledContract?.Cancel();
SongBar.Expanded = false;
scoreDisplay.FadeOut(100);
using (chat?.BeginDelayedSequence(500))
chat?.Expand();
}
private void expand()
{
scheduledContract?.Cancel();
chat?.Contract();
using (BeginDelayedSequence(300))
{
scoreDisplay.FadeIn(100);
SongBar.Expanded = true;
}
}
private void updateState()
{ {
try try
{ {
if (state.NewValue == TourneyState.Ranking) scheduledScreenChange?.Cancel();
if (State.Value == TourneyState.Ranking)
{ {
if (warmup.Value || CurrentMatch.Value == null) return; if (warmup.Value || CurrentMatch.Value == null) return;
@ -173,28 +200,7 @@ namespace osu.Game.Tournament.Screens.Gameplay
CurrentMatch.Value.Team2Score.Value++; CurrentMatch.Value.Team2Score.Value++;
} }
scheduledOperation?.Cancel(); switch (State.Value)
void expand()
{
chat?.Contract();
using (BeginDelayedSequence(300))
{
scoreDisplay.FadeIn(100);
SongBar.Expanded = true;
}
}
void contract()
{
SongBar.Expanded = false;
scoreDisplay.FadeOut(100);
using (chat?.BeginDelayedSequence(500))
chat?.Expand();
}
switch (state.NewValue)
{ {
case TourneyState.Idle: case TourneyState.Idle:
contract(); contract();
@ -208,30 +214,41 @@ namespace osu.Game.Tournament.Screens.Gameplay
if (lastState == TourneyState.Ranking && !warmup.Value) if (lastState == TourneyState.Ranking && !warmup.Value)
{ {
if (CurrentMatch.Value?.Completed.Value == true) if (CurrentMatch.Value?.Completed.Value == true)
scheduledOperation = Scheduler.AddDelayed(() => { sceneManager?.SetScreen(typeof(TeamWinScreen)); }, delay_before_progression); scheduledScreenChange = Scheduler.AddDelayed(() => { sceneManager?.SetScreen(typeof(TeamWinScreen)); }, delay_before_progression);
else if (CurrentMatch.Value?.Completed.Value == false) else if (CurrentMatch.Value?.Completed.Value == false)
scheduledOperation = Scheduler.AddDelayed(() => { sceneManager?.SetScreen(typeof(MapPoolScreen)); }, delay_before_progression); scheduledScreenChange = Scheduler.AddDelayed(() => { sceneManager?.SetScreen(typeof(MapPoolScreen)); }, delay_before_progression);
} }
} }
break; break;
case TourneyState.Ranking: case TourneyState.Ranking:
scheduledOperation = Scheduler.AddDelayed(contract, 10000); scheduledContract = Scheduler.AddDelayed(contract, 10000);
break; break;
default: default:
chat.Contract();
expand(); expand();
break; break;
} }
} }
finally finally
{ {
lastState = state.NewValue; lastState = State.Value;
} }
} }
public override void Hide()
{
scheduledScreenChange?.Cancel();
base.Hide();
}
public override void Show()
{
updateState();
base.Show();
}
private partial class ChromaArea : CompositeDrawable private partial class ChromaArea : CompositeDrawable
{ {
[Resolved] [Resolved]

View File

@ -13,6 +13,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Tournament.Models; using osu.Game.Tournament.Models;
using osu.Game.Tournament.Screens.Editors;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
using osuTK.Input; using osuTK.Input;
@ -25,7 +26,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components
private readonly bool editor; private readonly bool editor;
protected readonly FillFlowContainer<DrawableMatchTeam> Flow; protected readonly FillFlowContainer<DrawableMatchTeam> Flow;
private readonly Drawable selectionBox; private readonly Drawable selectionBox;
protected readonly Drawable CurrentMatchSelectionBox; private readonly Drawable currentMatchSelectionBox;
private Bindable<TournamentMatch> globalSelection; private Bindable<TournamentMatch> globalSelection;
[Resolved(CanBeNull = true)] [Resolved(CanBeNull = true)]
@ -41,35 +42,60 @@ namespace osu.Game.Tournament.Screens.Ladder.Components
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;
Margin = new MarginPadding(5); const float border_thickness = 5;
const float spacing = 2;
InternalChildren = new[] Margin = new MarginPadding(10);
InternalChildren = new Drawable[]
{ {
selectionBox = new Container
{
Scale = new Vector2(1.1f),
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Alpha = 0,
Colour = Color4.YellowGreen,
Child = new Box { RelativeSizeAxes = Axes.Both }
},
CurrentMatchSelectionBox = new Container
{
Scale = new Vector2(1.05f, 1.1f),
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Alpha = 0,
Colour = Color4.White,
Child = new Box { RelativeSizeAxes = Axes.Both }
},
Flow = new FillFlowContainer<DrawableMatchTeam> Flow = new FillFlowContainer<DrawableMatchTeam>
{ {
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical, Direction = FillDirection.Vertical,
Spacing = new Vector2(2) Spacing = new Vector2(spacing)
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(-10),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Child = selectionBox = new Container
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
Masking = true,
BorderColour = Color4.YellowGreen,
BorderThickness = border_thickness,
Child = new Box
{
RelativeSizeAxes = Axes.Both,
AlwaysPresent = true,
Alpha = 0,
}
},
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(-(spacing + border_thickness)),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Child = currentMatchSelectionBox = new Container
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
BorderColour = Color4.White,
BorderThickness = border_thickness,
Masking = true,
Child = new Box
{
RelativeSizeAxes = Axes.Both,
AlwaysPresent = true,
Alpha = 0,
}
},
} }
}; };
@ -97,12 +123,11 @@ namespace osu.Game.Tournament.Screens.Ladder.Components
Position = new Vector2(pos.NewValue.X, pos.NewValue.Y); Position = new Vector2(pos.NewValue.X, pos.NewValue.Y);
Changed?.Invoke(); Changed?.Invoke();
}, true); }, true);
updateTeams(); updateTeams();
} }
/// <summary> /// <summary>
/// Fired when somethign changed that requires a ladder redraw. /// Fired when something changed that requires a ladder redraw.
/// </summary> /// </summary>
public Action Changed; public Action Changed;
@ -126,9 +151,9 @@ namespace osu.Game.Tournament.Screens.Ladder.Components
private void updateCurrentMatch() private void updateCurrentMatch()
{ {
if (Match.Current.Value) if (Match.Current.Value)
CurrentMatchSelectionBox.Show(); currentMatchSelectionBox.Show();
else else
CurrentMatchSelectionBox.Hide(); currentMatchSelectionBox.Hide();
} }
private bool selected; private bool selected;
@ -225,10 +250,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components
if (editorInfo != null) if (editorInfo != null)
{ {
globalSelection = editorInfo.Selected.GetBoundCopy(); globalSelection = editorInfo.Selected.GetBoundCopy();
globalSelection.BindValueChanged(s => globalSelection.BindValueChanged(s => Selected = s.NewValue == Match, true);
{
if (s.NewValue != Match) Selected = false;
});
} }
} }
@ -312,8 +334,8 @@ namespace osu.Game.Tournament.Screens.Ladder.Components
private Vector2 snapToGrid(Vector2 pos) => private Vector2 snapToGrid(Vector2 pos) =>
new Vector2( new Vector2(
(int)(pos.X / 10) * 10, (int)(pos.X / LadderEditorScreen.GRID_SPACING) * LadderEditorScreen.GRID_SPACING,
(int)(pos.Y / 10) * 10 (int)(pos.Y / LadderEditorScreen.GRID_SPACING) * LadderEditorScreen.GRID_SPACING
); );
public void Remove() public void Remove()

View File

@ -62,22 +62,28 @@ namespace osu.Game.Tournament.Screens.Ladder.Components
// ensure any ongoing edits are committed out to the *current* selection before changing to a new one. // ensure any ongoing edits are committed out to the *current* selection before changing to a new one.
GetContainingInputManager().TriggerFocusContention(null); GetContainingInputManager().TriggerFocusContention(null);
roundDropdown.Current = selection.NewValue?.Round; // Required to avoid cyclic failure in BindableWithCurrent (TriggerChange called during the Current_Set process).
losersCheckbox.Current = selection.NewValue?.Losers; // Arguable a framework issue but since we haven't hit it anywhere else a local workaround seems best.
dateTimeBox.Current = selection.NewValue?.Date; roundDropdown.Current.ValueChanged -= roundDropdownChanged;
team1Dropdown.Current = selection.NewValue?.Team1; roundDropdown.Current = selection.NewValue.Round;
team2Dropdown.Current = selection.NewValue?.Team2; losersCheckbox.Current = selection.NewValue.Losers;
dateTimeBox.Current = selection.NewValue.Date;
team1Dropdown.Current = selection.NewValue.Team1;
team2Dropdown.Current = selection.NewValue.Team2;
roundDropdown.Current.ValueChanged += roundDropdownChanged;
}; };
}
roundDropdown.Current.ValueChanged += round => private void roundDropdownChanged(ValueChangedEvent<TournamentRound> round)
{
if (editorInfo.Selected.Value?.Date.Value < round.NewValue?.StartDate.Value)
{ {
if (editorInfo.Selected.Value?.Date.Value < round.NewValue?.StartDate.Value) editorInfo.Selected.Value.Date.Value = round.NewValue.StartDate.Value;
{ editorInfo.Selected.TriggerChange();
editorInfo.Selected.Value.Date.Value = round.NewValue.StartDate.Value; }
editorInfo.Selected.TriggerChange();
}
};
} }
protected override void LoadComplete() protected override void LoadComplete()

View File

@ -60,6 +60,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components
var topLeft = new Vector2(minX, minY); var topLeft = new Vector2(minX, minY);
OriginPosition = new Vector2(PathRadius);
Position = Parent.ToLocalSpace(topLeft); Position = Parent.ToLocalSpace(topLeft);
Vertices = points.Select(p => Parent.ToLocalSpace(p) - Parent.ToLocalSpace(topLeft)).ToList(); Vertices = points.Select(p => Parent.ToLocalSpace(p) - Parent.ToLocalSpace(topLeft)).ToList();
} }

View File

@ -37,6 +37,8 @@ namespace osu.Game.Tournament.Screens.MapPool
private OsuButton buttonRedPick; private OsuButton buttonRedPick;
private OsuButton buttonBluePick; private OsuButton buttonBluePick;
private ScheduledDelegate scheduledScreenChange;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(MatchIPCInfo ipc) private void load(MatchIPCInfo ipc)
{ {
@ -188,8 +190,6 @@ namespace osu.Game.Tournament.Screens.MapPool
setNextMode(); setNextMode();
} }
private ScheduledDelegate scheduledChange;
private void addForBeatmap(int beatmapId) private void addForBeatmap(int beatmapId)
{ {
if (CurrentMatch.Value == null) if (CurrentMatch.Value == null)
@ -216,12 +216,18 @@ namespace osu.Game.Tournament.Screens.MapPool
{ {
if (pickType == ChoiceType.Pick && CurrentMatch.Value.PicksBans.Any(i => i.Type == ChoiceType.Pick)) if (pickType == ChoiceType.Pick && CurrentMatch.Value.PicksBans.Any(i => i.Type == ChoiceType.Pick))
{ {
scheduledChange?.Cancel(); scheduledScreenChange?.Cancel();
scheduledChange = Scheduler.AddDelayed(() => { sceneManager?.SetScreen(typeof(GameplayScreen)); }, 10000); scheduledScreenChange = Scheduler.AddDelayed(() => { sceneManager?.SetScreen(typeof(GameplayScreen)); }, 10000);
} }
} }
} }
public override void Hide()
{
scheduledScreenChange?.Cancel();
base.Hide();
}
protected override void CurrentMatchChanged(ValueChangedEvent<TournamentMatch> match) protected override void CurrentMatchChanged(ValueChangedEvent<TournamentMatch> match)
{ {
base.CurrentMatchChanged(match); base.CurrentMatchChanged(match);

View File

@ -218,8 +218,6 @@ namespace osu.Game.Tournament.Screens.Schedule
Scale = new Vector2(0.8f); Scale = new Vector2(0.8f);
CurrentMatchSelectionBox.Scale = new Vector2(1.02f, 1.15f);
bool conditional = match is ConditionalTournamentMatch; bool conditional = match is ConditionalTournamentMatch;
if (conditional) if (conditional)

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

@ -17,6 +17,7 @@ using osu.Framework.Platform;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Cursor; using osu.Game.Graphics.Cursor;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
using osu.Game.Tournament.Models; using osu.Game.Tournament.Models;
using osuTK.Graphics; using osuTK.Graphics;
@ -35,14 +36,22 @@ namespace osu.Game.Tournament
public static readonly Color4 TEXT_COLOUR = Color4Extensions.FromHex("#fff"); public static readonly Color4 TEXT_COLOUR = Color4Extensions.FromHex("#fff");
private Drawable heightWarning; private Drawable heightWarning;
private Bindable<Size> windowSize;
private Bindable<WindowMode> windowMode; private Bindable<WindowMode> windowMode;
private readonly BindableSize windowSize = new BindableSize();
private LoadingSpinner loadingSpinner; private LoadingSpinner loadingSpinner;
[Cached(typeof(IDialogOverlay))]
private readonly DialogOverlay dialogOverlay = new DialogOverlay();
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(FrameworkConfigManager frameworkConfig, GameHost host) private void load(FrameworkConfigManager frameworkConfig, GameHost host)
{ {
windowSize = frameworkConfig.GetBindable<Size>(FrameworkSetting.WindowedSize); frameworkConfig.BindWith(FrameworkSetting.WindowedSize, windowSize);
windowSize.MinValue = new Size(TournamentSceneManager.REQUIRED_WIDTH, TournamentSceneManager.STREAM_AREA_HEIGHT);
windowMode = frameworkConfig.GetBindable<WindowMode>(FrameworkSetting.WindowMode); windowMode = frameworkConfig.GetBindable<WindowMode>(FrameworkSetting.WindowMode);
Add(loadingSpinner = new LoadingSpinner(true, true) Add(loadingSpinner = new LoadingSpinner(true, true)
@ -90,12 +99,12 @@ namespace osu.Game.Tournament
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Child = new TournamentSceneManager() Child = new TournamentSceneManager()
} },
dialogOverlay
}, drawables => }, drawables =>
{ {
loadingSpinner.Hide(); loadingSpinner.Hide();
loadingSpinner.Expire(); loadingSpinner.Expire();
AddRange(drawables); AddRange(drawables);
windowSize.BindValueChanged(size => ScheduleAfterChildren(() => windowSize.BindValueChanged(size => ScheduleAfterChildren(() =>

View File

@ -38,11 +38,14 @@ namespace osu.Game.Tournament
private Container screens; private Container screens;
private TourneyVideo video; private TourneyVideo video;
public const float CONTROL_AREA_WIDTH = 200; public const int CONTROL_AREA_WIDTH = 200;
public const float STREAM_AREA_WIDTH = 1366; public const int STREAM_AREA_WIDTH = 1366;
public const int STREAM_AREA_HEIGHT = (int)(STREAM_AREA_WIDTH / ASPECT_RATIO);
public const double REQUIRED_WIDTH = CONTROL_AREA_WIDTH * 2 + STREAM_AREA_WIDTH; public const float ASPECT_RATIO = 16 / 9f;
public const int REQUIRED_WIDTH = CONTROL_AREA_WIDTH * 2 + STREAM_AREA_WIDTH;
[Cached] [Cached]
private TournamentMatchChatDisplay chat = new TournamentMatchChatDisplay(); private TournamentMatchChatDisplay chat = new TournamentMatchChatDisplay();
@ -65,13 +68,20 @@ namespace osu.Game.Tournament
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Y,
X = CONTROL_AREA_WIDTH, X = CONTROL_AREA_WIDTH,
FillMode = FillMode.Fit, FillMode = FillMode.Fit,
FillAspectRatio = 16 / 9f, FillAspectRatio = ASPECT_RATIO,
Anchor = Anchor.TopLeft, Anchor = Anchor.TopLeft,
Origin = Anchor.TopLeft, Origin = Anchor.TopLeft,
Width = STREAM_AREA_WIDTH, Width = STREAM_AREA_WIDTH,
//Masking = true, //Masking = true,
Children = new Drawable[] Children = new Drawable[]
{ {
new Box
{
Colour = new Color4(20, 20, 20, 255),
Anchor = Anchor.TopRight,
RelativeSizeAxes = Axes.Both,
Width = 10,
},
video = new TourneyVideo("main", true) video = new TourneyVideo("main", true)
{ {
Loop = true, Loop = true,
@ -252,14 +262,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();
} }
} }
} }

View File

@ -14,6 +14,7 @@ using osu.Framework.Graphics;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Online.API;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.Notifications; using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets; using osu.Game.Rulesets;
@ -49,6 +50,9 @@ namespace osu.Game
[Resolved] [Resolved]
private INotificationOverlay? notificationOverlay { get; set; } private INotificationOverlay? notificationOverlay { get; set; }
[Resolved]
private IAPIProvider api { get; set; } = null!;
protected virtual int TimeToSleepDuringGameplay => 30000; protected virtual int TimeToSleepDuringGameplay => 30000;
protected override void LoadComplete() protected override void LoadComplete()
@ -118,10 +122,27 @@ namespace osu.Game
realmAccess.Run(r => realmAccess.Run(r =>
{ {
foreach (var b in r.All<BeatmapInfo>().Where(b => b.StarRating < 0 || (b.OnlineID > 0 && b.LastOnlineUpdate == null))) // BeatmapProcessor is responsible for both online and local processing.
// In the case a user isn't logged in, it won't update LastOnlineUpdate and therefore re-queue,
// causing overhead from the non-online processing to redundantly run every startup.
//
// We may eventually consider making the Process call more specific (or avoid this in any number
// of other possible ways), but for now avoid queueing if the user isn't logged in at startup.
if (api.IsLoggedIn)
{ {
Debug.Assert(b.BeatmapSet != null); foreach (var b in r.All<BeatmapInfo>().Where(b => b.StarRating < 0 || (b.OnlineID > 0 && b.LastOnlineUpdate == null)))
beatmapSetIds.Add(b.BeatmapSet.ID); {
Debug.Assert(b.BeatmapSet != null);
beatmapSetIds.Add(b.BeatmapSet.ID);
}
}
else
{
foreach (var b in r.All<BeatmapInfo>().Where(b => b.StarRating < 0))
{
Debug.Assert(b.BeatmapSet != null);
beatmapSetIds.Add(b.BeatmapSet.ID);
}
} }
}); });

View File

@ -29,7 +29,7 @@ namespace osu.Game.Beatmaps
/// </summary> /// </summary>
public class BeatmapImporter : RealmArchiveModelImporter<BeatmapSetInfo> public class BeatmapImporter : RealmArchiveModelImporter<BeatmapSetInfo>
{ {
public override IEnumerable<string> HandledExtensions => new[] { ".osz" }; public override IEnumerable<string> HandledExtensions => new[] { ".osz", ".olz" };
protected override string[] HashableFileTypes => new[] { ".osu" }; protected override string[] HashableFileTypes => new[] { ".osu" };
@ -145,7 +145,7 @@ namespace osu.Game.Beatmaps
} }
} }
protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path).ToLowerInvariant() == ".osz"; protected override bool ShouldDeleteArchive(string path) => HandledExtensions.Contains(Path.GetExtension(path).ToLowerInvariant());
protected override void Populate(BeatmapSetInfo beatmapSet, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default) protected override void Populate(BeatmapSetInfo beatmapSet, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default)
{ {

View File

@ -40,7 +40,9 @@ namespace osu.Game.Beatmaps
private readonly WorkingBeatmapCache workingBeatmapCache; private readonly WorkingBeatmapCache workingBeatmapCache;
private readonly LegacyBeatmapExporter beatmapExporter; private readonly BeatmapExporter beatmapExporter;
private readonly LegacyBeatmapExporter legacyBeatmapExporter;
public ProcessBeatmapDelegate? ProcessBeatmap { private get; set; } public ProcessBeatmapDelegate? ProcessBeatmap { private get; set; }
@ -77,7 +79,12 @@ namespace osu.Game.Beatmaps
workingBeatmapCache = CreateWorkingBeatmapCache(audioManager, gameResources, userResources, defaultBeatmap, host); workingBeatmapCache = CreateWorkingBeatmapCache(audioManager, gameResources, userResources, defaultBeatmap, host);
beatmapExporter = new LegacyBeatmapExporter(storage) beatmapExporter = new BeatmapExporter(storage)
{
PostNotification = obj => PostNotification?.Invoke(obj)
};
legacyBeatmapExporter = new LegacyBeatmapExporter(storage)
{ {
PostNotification = obj => PostNotification?.Invoke(obj) PostNotification = obj => PostNotification?.Invoke(obj)
}; };
@ -402,6 +409,8 @@ namespace osu.Game.Beatmaps
public Task Export(BeatmapSetInfo beatmap) => beatmapExporter.ExportAsync(beatmap.ToLive(Realm)); public Task Export(BeatmapSetInfo beatmap) => beatmapExporter.ExportAsync(beatmap.ToLive(Realm));
public Task ExportLegacy(BeatmapSetInfo beatmap) => legacyBeatmapExporter.ExportAsync(beatmap.ToLive(Realm));
private void updateHashAndMarkDirty(BeatmapSetInfo setInfo) private void updateHashAndMarkDirty(BeatmapSetInfo setInfo)
{ {
setInfo.Hash = beatmapImporter.ComputeHash(setInfo); setInfo.Hash = beatmapImporter.ComputeHash(setInfo);

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.Platform;
using osu.Game.Beatmaps;
namespace osu.Game.Database
{
/// <summary>
/// Exporter for beatmap archives.
/// This is not for legacy purposes and works for lazer only.
/// </summary>
public class BeatmapExporter : LegacyArchiveExporter<BeatmapSetInfo>
{
public BeatmapExporter(Storage storage)
: base(storage)
{
}
protected override string FileExtension => @".olz";
}
}

View File

@ -39,7 +39,7 @@ namespace osu.Game.Database
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
using (var stream = UserFileStorage.GetStream(file.File.GetStoragePath())) using (var stream = GetFileContents(model, file))
{ {
if (stream == null) if (stream == null)
{ {
@ -65,5 +65,7 @@ namespace osu.Game.Database
} }
} }
} }
protected virtual Stream? GetFileContents(TModel model, INamedFileUsage file) => UserFileStorage.GetStream(file.File.GetStoragePath());
} }
} }

View File

@ -1,11 +1,25 @@
// 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.IO;
using System.Linq;
using System.Text;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats;
using osu.Game.IO;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Database namespace osu.Game.Database
{ {
/// <summary>
/// Exporter for osu!stable legacy beatmap archives.
/// Converts all beatmaps in the set to legacy format and exports it as a legacy package.
/// </summary>
public class LegacyBeatmapExporter : LegacyArchiveExporter<BeatmapSetInfo> public class LegacyBeatmapExporter : LegacyArchiveExporter<BeatmapSetInfo>
{ {
public LegacyBeatmapExporter(Storage storage) public LegacyBeatmapExporter(Storage storage)
@ -13,6 +27,72 @@ namespace osu.Game.Database
{ {
} }
protected override Stream? GetFileContents(BeatmapSetInfo model, INamedFileUsage file)
{
bool isBeatmap = model.Beatmaps.Any(o => o.Hash == file.File.Hash);
if (!isBeatmap)
return base.GetFileContents(model, file);
// Read the beatmap contents and skin
using var contentStream = base.GetFileContents(model, file);
if (contentStream == null)
return null;
using var contentStreamReader = new LineBufferedReader(contentStream);
var beatmapContent = new LegacyBeatmapDecoder().Decode(contentStreamReader);
using var skinStream = base.GetFileContents(model, file);
if (skinStream == null)
return null;
using var skinStreamReader = new LineBufferedReader(skinStream);
var beatmapSkin = new LegacySkin(new SkinInfo(), null!)
{
Configuration = new LegacySkinDecoder().Decode(skinStreamReader)
};
// Convert beatmap elements to be compatible with legacy format
// So we truncate time and position values to integers, and convert paths with multiple segments to bezier curves
foreach (var controlPoint in beatmapContent.ControlPointInfo.AllControlPoints)
controlPoint.Time = Math.Floor(controlPoint.Time);
foreach (var hitObject in beatmapContent.HitObjects)
{
// Truncate end time before truncating start time because end time is dependent on start time
if (hitObject is IHasDuration hasDuration && hitObject is not IHasPath)
hasDuration.Duration = Math.Floor(hasDuration.EndTime) - Math.Floor(hitObject.StartTime);
hitObject.StartTime = Math.Floor(hitObject.StartTime);
if (hitObject is not IHasPath hasPath || BezierConverter.CountSegments(hasPath.Path.ControlPoints) <= 1) continue;
var newControlPoints = BezierConverter.ConvertToModernBezier(hasPath.Path.ControlPoints);
// Truncate control points to integer positions
foreach (var pathControlPoint in newControlPoints)
{
pathControlPoint.Position = new Vector2(
(float)Math.Floor(pathControlPoint.Position.X),
(float)Math.Floor(pathControlPoint.Position.Y));
}
hasPath.Path.ControlPoints.Clear();
hasPath.Path.ControlPoints.AddRange(newControlPoints);
}
// Encode to legacy format
var stream = new MemoryStream();
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
new LegacyBeatmapEncoder(beatmapContent, beatmapSkin).Encode(sw);
stream.Seek(0, SeekOrigin.Begin);
return stream;
}
protected override string FileExtension => @".osz"; protected override string FileExtension => @".osz";
} }
} }

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

@ -39,6 +39,11 @@ namespace osu.Game.Localisation
/// </summary> /// </summary>
public static LocalisableString Default => new TranslatableString(getKey(@"default"), @"Default"); public static LocalisableString Default => new TranslatableString(getKey(@"default"), @"Default");
/// <summary>
/// "Export"
/// </summary>
public static LocalisableString Export => new TranslatableString(getKey(@"export"), @"Export");
/// <summary> /// <summary>
/// "Width" /// "Width"
/// </summary> /// </summary>

View File

@ -35,9 +35,14 @@ namespace osu.Game.Localisation
public static LocalisableString SetPreviewPointToCurrent => new TranslatableString(getKey(@"set_preview_point_to_current"), @"Set preview point to current time"); public static LocalisableString SetPreviewPointToCurrent => new TranslatableString(getKey(@"set_preview_point_to_current"), @"Set preview point to current time");
/// <summary> /// <summary>
/// "Export package" /// "For editing (.olz)"
/// </summary> /// </summary>
public static LocalisableString ExportPackage => new TranslatableString(getKey(@"export_package"), @"Export package"); public static LocalisableString ExportForEditing => new TranslatableString(getKey(@"export_for_editing"), @"For editing (.olz)");
/// <summary>
/// "For compatibility (.osz)"
/// </summary>
public static LocalisableString ExportForCompatibility => new TranslatableString(getKey(@"export_for_compatibility"), @"For compatibility (.osz)");
/// <summary> /// <summary>
/// "Create new difficulty" /// "Create new difficulty"

View File

@ -425,7 +425,7 @@ namespace osu.Game.Online.Leaderboards
if (Score.Files.Count > 0) if (Score.Files.Count > 0)
{ {
items.Add(new OsuMenuItem("Export", MenuItemType.Standard, () => scoreManager.Export(Score))); items.Add(new OsuMenuItem(Localisation.CommonStrings.Export, MenuItemType.Standard, () => scoreManager.Export(Score)));
items.Add(new OsuMenuItem(CommonStrings.ButtonsDelete, MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(Score)))); items.Add(new OsuMenuItem(CommonStrings.ButtonsDelete, MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(Score))));
} }

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

@ -38,6 +38,9 @@ namespace osu.Game.Rulesets.Edit
// Timing // Timing
new CheckPreviewTime(), new CheckPreviewTime(),
// Events
new CheckBreaks()
}; };
public IEnumerable<Issue> Run(BeatmapVerifierContext context) public IEnumerable<Issue> Run(BeatmapVerifierContext context)

View File

@ -0,0 +1,94 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets.Edit.Checks.Components;
using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Edit.Checks
{
public class CheckBreaks : ICheck
{
// Breaks may be off by 1 ms.
private const int leniency_threshold = 1;
private const double minimum_gap_before_break = 200;
// Break end time depends on the upcoming object's pre-empt time.
// As things stand, "pre-empt time" is only defined for osu! standard
// This is a generic value representing AR=10
// Relevant: https://github.com/ppy/osu/issues/14330#issuecomment-1002158551
private const double min_end_threshold = 450;
public CheckMetadata Metadata => new CheckMetadata(CheckCategory.Events, "Breaks not achievable using the editor");
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
{
new IssueTemplateEarlyStart(this),
new IssueTemplateLateEnd(this),
new IssueTemplateTooShort(this)
};
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
{
var startTimes = context.Beatmap.HitObjects.Select(ho => ho.StartTime).OrderBy(x => x).ToList();
var endTimes = context.Beatmap.HitObjects.Select(ho => ho.GetEndTime()).OrderBy(x => x).ToList();
foreach (var breakPeriod in context.Beatmap.Breaks)
{
if (breakPeriod.Duration < BreakPeriod.MIN_BREAK_DURATION)
yield return new IssueTemplateTooShort(this).Create(breakPeriod.StartTime);
int previousObjectEndTimeIndex = endTimes.BinarySearch(breakPeriod.StartTime);
if (previousObjectEndTimeIndex < 0) previousObjectEndTimeIndex = ~previousObjectEndTimeIndex - 1;
if (previousObjectEndTimeIndex >= 0)
{
double gapBeforeBreak = breakPeriod.StartTime - endTimes[previousObjectEndTimeIndex];
if (gapBeforeBreak < minimum_gap_before_break - leniency_threshold)
yield return new IssueTemplateEarlyStart(this).Create(breakPeriod.StartTime, minimum_gap_before_break - gapBeforeBreak);
}
int nextObjectStartTimeIndex = startTimes.BinarySearch(breakPeriod.EndTime);
if (nextObjectStartTimeIndex < 0) nextObjectStartTimeIndex = ~nextObjectStartTimeIndex;
if (nextObjectStartTimeIndex < startTimes.Count)
{
double gapAfterBreak = startTimes[nextObjectStartTimeIndex] - breakPeriod.EndTime;
if (gapAfterBreak < min_end_threshold - leniency_threshold)
yield return new IssueTemplateLateEnd(this).Create(breakPeriod.StartTime, min_end_threshold - gapAfterBreak);
}
}
}
public class IssueTemplateEarlyStart : IssueTemplate
{
public IssueTemplateEarlyStart(ICheck check)
: base(check, IssueType.Problem, "Break starts {0} ms early.")
{
}
public Issue Create(double startTime, double diff) => new Issue(startTime, this, (int)diff);
}
public class IssueTemplateLateEnd : IssueTemplate
{
public IssueTemplateLateEnd(ICheck check)
: base(check, IssueType.Problem, "Break ends {0} ms late.")
{
}
public Issue Create(double startTime, double diff) => new Issue(startTime, this, (int)diff);
}
public class IssueTemplateTooShort : IssueTemplate
{
public IssueTemplateTooShort(ICheck check)
: base(check, IssueType.Warning, "Break is non-functional due to being less than {0} ms.")
{
}
public Issue Create(double startTime) => new Issue(startTime, this, BreakPeriod.MIN_BREAK_DURATION);
}
}
}

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

@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Edit
/// </summary> /// </summary>
public event Action<SelectionBlueprint<T>> Deselected; public event Action<SelectionBlueprint<T>> Deselected;
public override bool HandlePositionalInput => ShouldBeAlive; public override bool HandlePositionalInput => IsSelectable;
public override bool RemoveWhenNotAlive => false; public override bool RemoveWhenNotAlive => false;
protected SelectionBlueprint(T item) protected SelectionBlueprint(T item)
@ -125,6 +125,11 @@ namespace osu.Game.Rulesets.Edit
/// </summary> /// </summary>
public virtual MenuItem[] ContextMenuItems => Array.Empty<MenuItem>(); public virtual MenuItem[] ContextMenuItems => Array.Empty<MenuItem>();
/// <summary>
/// Whether the <see cref="SelectionBlueprint{T}"/> can be currently selected via a click or a drag box.
/// </summary>
public virtual bool IsSelectable => ShouldBeAlive && IsPresent;
/// <summary> /// <summary>
/// The screen-space main point that causes this <see cref="HitObjectSelectionBlueprint"/> to be selected via a drag. /// The screen-space main point that causes this <see cref="HitObjectSelectionBlueprint"/> to be selected via a drag.
/// </summary> /// </summary>

View File

@ -39,6 +39,13 @@ namespace osu.Game.Rulesets.Objects
new[] { new Vector2d(1, 0), new Vector2d(1, 1.2447058f), new Vector2d(-0.8526471f, 2.118367f), new Vector2d(-2.6211002f, 7.854936e-06f), new Vector2d(-0.8526448f, -2.118357f), new Vector2d(1, -1.2447058f), new Vector2d(1, 0) }) new[] { new Vector2d(1, 0), new Vector2d(1, 1.2447058f), new Vector2d(-0.8526471f, 2.118367f), new Vector2d(-2.6211002f, 7.854936e-06f), new Vector2d(-0.8526448f, -2.118357f), new Vector2d(1, -1.2447058f), new Vector2d(1, 0) })
}; };
/// <summary>
/// Counts the number of segments in a slider path.
/// </summary>
/// <param name="controlPoints">The control points of the path.</param>
/// <returns>The number of segments in a slider path.</returns>
public static int CountSegments(IList<PathControlPoint> controlPoints) => controlPoints.Where((t, i) => t.Type != null && i < controlPoints.Count - 1).Count();
/// <summary> /// <summary>
/// Converts a slider path to bezier control point positions compatible with the legacy osu! client. /// Converts a slider path to bezier control point positions compatible with the legacy osu! client.
/// </summary> /// </summary>

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,10 @@
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.Shapes;
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 +18,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();
@ -157,7 +201,17 @@ namespace osu.Game.Screens.Edit.Components.Menus
public DrawableSpacer(MenuItem item) public DrawableSpacer(MenuItem item)
: base(item) : base(item)
{ {
Scale = new Vector2(1, 0.3f); Scale = new Vector2(1, 0.6f);
AddInternal(new Box
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Colour = BackgroundColourHover,
RelativeSizeAxes = Axes.X,
Height = 2f,
Width = 0.8f,
});
} }
protected override bool OnHover(HoverEvent e) => true; protected override bool OnHover(HoverEvent e) => true;

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

@ -487,7 +487,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
break; break;
case SelectionState.NotSelected: case SelectionState.NotSelected:
if (blueprint.IsAlive && blueprint.IsPresent && quad.Contains(blueprint.ScreenSpaceSelectionPoint)) if (blueprint.IsSelectable && quad.Contains(blueprint.ScreenSpaceSelectionPoint))
blueprint.Select(); blueprint.Select();
break; break;
} }

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,
}, },
}, },
@ -997,23 +997,40 @@ namespace osu.Game.Screens.Edit
private List<MenuItem> createFileMenuItems() => new List<MenuItem> private List<MenuItem> createFileMenuItems() => new List<MenuItem>
{ {
new EditorMenuItem(WebCommonStrings.ButtonsSave, MenuItemType.Standard, () => Save()),
new EditorMenuItem(EditorStrings.ExportPackage, MenuItemType.Standard, exportBeatmap) { Action = { Disabled = !RuntimeInfo.IsDesktop } },
new EditorMenuItemSpacer(),
createDifficultyCreationMenu(), createDifficultyCreationMenu(),
createDifficultySwitchMenu(), createDifficultySwitchMenu(),
new EditorMenuItemSpacer(), new EditorMenuItemSpacer(),
new EditorMenuItem(EditorStrings.DeleteDifficulty, MenuItemType.Standard, deleteDifficulty) { Action = { Disabled = Beatmap.Value.BeatmapSetInfo.Beatmaps.Count < 2 } }, new EditorMenuItem(EditorStrings.DeleteDifficulty, MenuItemType.Standard, deleteDifficulty) { Action = { Disabled = Beatmap.Value.BeatmapSetInfo.Beatmaps.Count < 2 } },
new EditorMenuItemSpacer(), new EditorMenuItemSpacer(),
new EditorMenuItem(WebCommonStrings.ButtonsSave, MenuItemType.Standard, () => Save()),
createExportMenu(),
new EditorMenuItemSpacer(),
new EditorMenuItem(CommonStrings.Exit, MenuItemType.Standard, this.Exit) new EditorMenuItem(CommonStrings.Exit, MenuItemType.Standard, this.Exit)
}; };
private EditorMenuItem createExportMenu()
{
var exportItems = new List<MenuItem>
{
new EditorMenuItem(EditorStrings.ExportForEditing, MenuItemType.Standard, exportBeatmap) { Action = { Disabled = !RuntimeInfo.IsDesktop } },
new EditorMenuItem(EditorStrings.ExportForCompatibility, MenuItemType.Standard, exportLegacyBeatmap) { Action = { Disabled = !RuntimeInfo.IsDesktop } },
};
return new EditorMenuItem(CommonStrings.Export) { Items = exportItems };
}
private void exportBeatmap() private void exportBeatmap()
{ {
Save(); Save();
beatmapManager.Export(Beatmap.Value.BeatmapSetInfo); beatmapManager.Export(Beatmap.Value.BeatmapSetInfo);
} }
private void exportLegacyBeatmap()
{
Save();
beatmapManager.ExportLegacy(Beatmap.Value.BeatmapSetInfo);
}
/// <summary> /// <summary>
/// Beatmaps of the currently edited set, grouped by ruleset and ordered by difficulty. /// Beatmaps of the currently edited set, grouped by ruleset and ordered by difficulty.
/// </summary> /// </summary>

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

@ -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

@ -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.720.0" /> <PackageReference Include="ppy.osu.Framework" Version="2023.724.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2023.719.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2023.719.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" />

Some files were not shown because too many files have changed in this diff Show More