mirror of
https://github.com/ppy/osu.git
synced 2025-03-10 03:57:20 +08:00
Merge branch 'master' into pp-dev
This commit is contained in:
commit
87b6dddd11
@ -9,7 +9,7 @@
|
||||
<GenerateProgramFile>false</GenerateProgramFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
|
||||
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
</ItemGroup>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<GenerateProgramFile>false</GenerateProgramFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
|
||||
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
</ItemGroup>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<GenerateProgramFile>false</GenerateProgramFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
|
||||
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
</ItemGroup>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<GenerateProgramFile>false</GenerateProgramFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
|
||||
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
</ItemGroup>
|
||||
|
@ -10,7 +10,7 @@
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.204.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.225.0" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||
|
@ -21,7 +21,7 @@ namespace osu.Android
|
||||
[Cached]
|
||||
private readonly OsuGameActivity gameActivity;
|
||||
|
||||
protected override Vector2 ScalingContainerTargetDrawSize => new Vector2(1024, 1024 * DrawHeight / DrawWidth);
|
||||
public override Vector2 ScalingContainerTargetDrawSize => new Vector2(1024, 1024 * DrawHeight / DrawWidth);
|
||||
|
||||
public OsuGameAndroid(OsuGameActivity activity)
|
||||
: base(null)
|
||||
|
@ -24,7 +24,7 @@
|
||||
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="System.IO.Packaging" Version="9.0.0" />
|
||||
<PackageReference Include="System.IO.Packaging" Version="9.0.2" />
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||
<PackageReference Include="Velopack" Version="0.0.1053" />
|
||||
</ItemGroup>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
|
||||
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
</ItemGroup>
|
||||
|
@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
@ -12,6 +13,7 @@ using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
using Direction = osu.Framework.Graphics.Direction;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Edit
|
||||
@ -38,6 +40,13 @@ namespace osu.Game.Rulesets.Catch.Edit
|
||||
return true;
|
||||
}
|
||||
|
||||
moveSelection(deltaX);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void moveSelection(float deltaX)
|
||||
{
|
||||
EditorBeatmap.PerformOnSelection(h =>
|
||||
{
|
||||
if (!(h is CatchHitObject catchObject)) return;
|
||||
@ -48,7 +57,60 @@ namespace osu.Game.Rulesets.Catch.Edit
|
||||
foreach (var nested in catchObject.NestedHitObjects.OfType<CatchHitObject>())
|
||||
nested.OriginalX += deltaX;
|
||||
});
|
||||
}
|
||||
|
||||
private bool nudgeMovementActive;
|
||||
|
||||
protected override bool OnKeyDown(KeyDownEvent e)
|
||||
{
|
||||
// Until the keys below are global actions, this will prevent conflicts with "seek between sample points"
|
||||
// which has a default of ctrl+shift+arrows.
|
||||
if (e.ShiftPressed)
|
||||
return false;
|
||||
|
||||
if (e.ControlPressed)
|
||||
{
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.Left:
|
||||
return nudgeSelection(-1);
|
||||
|
||||
case Key.Right:
|
||||
return nudgeSelection(1);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void OnKeyUp(KeyUpEvent e)
|
||||
{
|
||||
base.OnKeyUp(e);
|
||||
|
||||
if (nudgeMovementActive && !e.ControlPressed)
|
||||
{
|
||||
EditorBeatmap.EndChange();
|
||||
nudgeMovementActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move the current selection spatially by the specified delta, in gamefield coordinates (ie. the same coordinates as the blueprints).
|
||||
/// </summary>
|
||||
private bool nudgeSelection(float deltaX)
|
||||
{
|
||||
if (!nudgeMovementActive)
|
||||
{
|
||||
nudgeMovementActive = true;
|
||||
EditorBeatmap.BeginChange();
|
||||
}
|
||||
|
||||
var firstBlueprint = SelectedBlueprints.FirstOrDefault();
|
||||
|
||||
if (firstBlueprint == null)
|
||||
return false;
|
||||
|
||||
moveSelection(deltaX);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.UI;
|
||||
@ -15,6 +16,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
protected override Container<Drawable> Content => content;
|
||||
private readonly Container content;
|
||||
|
||||
private readonly Container scaleContainer;
|
||||
|
||||
public CatchPlayfieldAdjustmentContainer()
|
||||
{
|
||||
const float base_game_width = 1024f;
|
||||
@ -26,30 +29,49 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
InternalChild = new Container
|
||||
InternalChild = scaleContainer = new Container
|
||||
{
|
||||
// This container limits vertical visibility of the playfield to ensure fairness between wide and tall resolutions (i.e. tall resolutions should not see more fruits).
|
||||
// Note that the container still extends across the screen horizontally, so that hit explosions at the sides of the playfield do not get cut off.
|
||||
Name = "Visible area",
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = base_game_height + extra_bottom_space,
|
||||
Y = extra_bottom_space / 2,
|
||||
Masking = true,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new Container
|
||||
{
|
||||
Name = "Playable area",
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
// playfields in stable are positioned vertically at three fourths the difference between the playfield height and the window height in stable.
|
||||
Y = base_game_height * ((1 - playfield_size_adjust) / 4 * 3),
|
||||
Size = new Vector2(base_game_width, base_game_height) * playfield_size_adjust,
|
||||
Child = content = new ScalingContainer { RelativeSizeAxes = Axes.Both }
|
||||
},
|
||||
// This container limits vertical visibility of the playfield to ensure fairness between wide and tall resolutions (i.e. tall resolutions should not see more fruits).
|
||||
// Note that the container still extends across the screen horizontally, so that hit explosions at the sides of the playfield do not get cut off.
|
||||
Name = "Visible area",
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = base_game_height + extra_bottom_space,
|
||||
Y = extra_bottom_space / 2,
|
||||
Masking = true,
|
||||
Child = new Container
|
||||
{
|
||||
Name = "Playable area",
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
// playfields in stable are positioned vertically at three fourths the difference between the playfield height and the window height in stable.
|
||||
Y = base_game_height * ((1 - playfield_size_adjust) / 4 * 3),
|
||||
Size = new Vector2(base_game_width, base_game_height) * playfield_size_adjust,
|
||||
Child = content = new ScalingContainer { RelativeSizeAxes = Axes.Both }
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuGame? osuGame)
|
||||
{
|
||||
if (osuGame != null)
|
||||
{
|
||||
// on mobile platforms where the base aspect ratio is wider, the catch playfield
|
||||
// needs to be scaled down to remain playable.
|
||||
const float base_aspect_ratio = 1024f / 768f;
|
||||
float aspectRatio = osuGame.ScalingContainerTargetDrawSize.X / osuGame.ScalingContainerTargetDrawSize.Y;
|
||||
scaleContainer.Scale = new Vector2(base_aspect_ratio / aspectRatio);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="Container"/> which scales its content relative to a target width.
|
||||
/// </summary>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
|
||||
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
</ItemGroup>
|
||||
|
@ -24,11 +24,15 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
{
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMissTail() => CreateModTest(new ModTestData
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public void TestMissTail(bool tailMiss) => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new OsuModSuddenDeath(),
|
||||
PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(false),
|
||||
Mod = new OsuModSuddenDeath
|
||||
{
|
||||
FailOnSliderTail = { Value = tailMiss }
|
||||
},
|
||||
PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(tailMiss),
|
||||
Autoplay = false,
|
||||
CreateBeatmap = () => new Beatmap
|
||||
{
|
||||
|
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
|
||||
<PackageReference Include="Moq" Version="4.18.4" />
|
||||
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
|
@ -626,7 +626,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
|
||||
{
|
||||
if (BodyPiece.ReceivePositionalInputAt(screenSpacePos) && DrawableObject.Body.Alpha > 0)
|
||||
if (BodyPiece.ReceivePositionalInputAt(screenSpacePos) && (IsSelected || DrawableObject.Body.Alpha > 0))
|
||||
return true;
|
||||
|
||||
if (ControlPointVisualiser == null)
|
||||
|
@ -1,10 +1,12 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osuTK;
|
||||
|
||||
@ -14,7 +16,9 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
public override double ReadCurrentDistanceSnap(HitObject before, HitObject after)
|
||||
{
|
||||
float expectedDistance = DurationToDistance(after.StartTime - before.GetEndTime(), before.StartTime);
|
||||
var lastObjectWithVelocity = EditorBeatmap.HitObjects.TakeWhile(ho => ho != after).OfType<IHasSliderVelocity>().LastOrDefault();
|
||||
|
||||
float expectedDistance = DurationToDistance(after.StartTime - before.GetEndTime(), before.StartTime, lastObjectWithVelocity);
|
||||
float actualDistance = Vector2.Distance(((OsuHitObject)before).EndPosition, ((OsuHitObject)after).Position);
|
||||
|
||||
return actualDistance / expectedDistance;
|
||||
|
@ -40,6 +40,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
SelectionBox.CanReverse = EditorBeatmap.SelectedHitObjects.Count > 1 || EditorBeatmap.SelectedHitObjects.Any(s => s is Slider);
|
||||
}
|
||||
|
||||
private bool nudgeMovementActive;
|
||||
|
||||
protected override bool OnKeyDown(KeyDownEvent e)
|
||||
{
|
||||
if (e.Key == Key.M && e.ControlPressed && e.ShiftPressed)
|
||||
@ -48,9 +50,43 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
return true;
|
||||
}
|
||||
|
||||
// Until the keys below are global actions, this will prevent conflicts with "seek between sample points"
|
||||
// which has a default of ctrl+shift+arrows.
|
||||
if (e.ShiftPressed)
|
||||
return false;
|
||||
|
||||
if (e.ControlPressed)
|
||||
{
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.Left:
|
||||
return nudgeSelection(new Vector2(-1, 0));
|
||||
|
||||
case Key.Right:
|
||||
return nudgeSelection(new Vector2(1, 0));
|
||||
|
||||
case Key.Up:
|
||||
return nudgeSelection(new Vector2(0, -1));
|
||||
|
||||
case Key.Down:
|
||||
return nudgeSelection(new Vector2(0, 1));
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void OnKeyUp(KeyUpEvent e)
|
||||
{
|
||||
base.OnKeyUp(e);
|
||||
|
||||
if (nudgeMovementActive && !e.ControlPressed)
|
||||
{
|
||||
EditorBeatmap.EndChange();
|
||||
nudgeMovementActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool HandleMovement(MoveSelectionEvent<HitObject> moveEvent)
|
||||
{
|
||||
var hitObjects = selectedMovableObjects;
|
||||
@ -70,6 +106,13 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
if (hitObjects.Any(h => Precision.AlmostEquals(localDelta, -h.StackOffset)))
|
||||
return true;
|
||||
|
||||
moveObjects(hitObjects, localDelta);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void moveObjects(OsuHitObject[] hitObjects, Vector2 localDelta)
|
||||
{
|
||||
// this will potentially move the selection out of bounds...
|
||||
foreach (var h in hitObjects)
|
||||
h.Position += localDelta;
|
||||
@ -81,7 +124,26 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
// this intentionally bypasses the editor `UpdateState()` / beatmap processor flow for performance reasons,
|
||||
// as the entire flow is too expensive to run on every movement.
|
||||
Scheduler.AddOnce(OsuBeatmapProcessor.ApplyStacking, EditorBeatmap);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move the current selection spatially by the specified delta, in gamefield coordinates (ie. the same coordinates as the blueprints).
|
||||
/// </summary>
|
||||
/// <param name="delta"></param>
|
||||
private bool nudgeSelection(Vector2 delta)
|
||||
{
|
||||
if (!nudgeMovementActive)
|
||||
{
|
||||
nudgeMovementActive = true;
|
||||
EditorBeatmap.BeginChange();
|
||||
}
|
||||
|
||||
var firstBlueprint = SelectedBlueprints.FirstOrDefault();
|
||||
|
||||
if (firstBlueprint == null)
|
||||
return false;
|
||||
|
||||
moveObjects(selectedMovableObjects, delta);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
@ -63,13 +62,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
private partial class ApproachRateSettingsControl : DifficultyAdjustSettingsControl
|
||||
{
|
||||
protected override RoundedSliderBar<float> CreateSlider(BindableNumber<float> current) =>
|
||||
new ApproachRateSlider
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Current = current,
|
||||
KeyboardStep = 0.1f,
|
||||
};
|
||||
protected override RoundedSliderBar<float> CreateSlider(BindableNumber<float> current) => new ApproachRateSlider();
|
||||
|
||||
/// <summary>
|
||||
/// A slider bar with more detailed approach rate info for its given value
|
||||
|
@ -3,7 +3,12 @@
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
@ -13,5 +18,19 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
typeof(OsuModTargetPractice),
|
||||
}).ToArray();
|
||||
|
||||
[SettingSource("Also fail when missing a slider tail")]
|
||||
public BindableBool FailOnSliderTail { get; } = new BindableBool();
|
||||
|
||||
protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result)
|
||||
{
|
||||
if (base.FailCondition(healthProcessor, result))
|
||||
return true;
|
||||
|
||||
if (FailOnSliderTail.Value && result.HitObject is SliderTailCircle && !result.IsHit)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
|
||||
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
</ItemGroup>
|
||||
|
@ -62,10 +62,7 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
||||
if (h is not TaikoStrongableHitObject strongable) return;
|
||||
|
||||
if (strongable.IsStrong != state)
|
||||
{
|
||||
strongable.IsStrong = state;
|
||||
EditorBeatmap.Update(strongable);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -77,10 +74,7 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
||||
EditorBeatmap.PerformOnSelection(h =>
|
||||
{
|
||||
if (h is Hit taikoHit)
|
||||
{
|
||||
taikoHit.Type = state ? HitType.Rim : HitType.Centre;
|
||||
EditorBeatmap.Update(h);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -4,9 +4,7 @@
|
||||
#nullable disable
|
||||
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Rulesets.Taiko.Skinning.Default;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
@ -25,8 +23,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
{
|
||||
}
|
||||
|
||||
protected override void UpdateInitialTransforms() => this.FadeOut();
|
||||
|
||||
public void TriggerResult(bool hit)
|
||||
{
|
||||
HitObject.StartTime = Time.Current;
|
||||
@ -43,7 +39,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
|
||||
public override bool OnPressed(KeyBindingPressEvent<TaikoAction> e) => false;
|
||||
|
||||
protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.DrumRollTick),
|
||||
_ => new TickPiece());
|
||||
protected override SkinnableDrawable CreateMainPiece() => null;
|
||||
}
|
||||
}
|
||||
|
@ -154,9 +154,13 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
if (MainPiece != null)
|
||||
Content.Remove(MainPiece, true);
|
||||
|
||||
Content.Add(MainPiece = CreateMainPiece());
|
||||
MainPiece = CreateMainPiece();
|
||||
|
||||
if (MainPiece != null)
|
||||
Content.Add(MainPiece);
|
||||
}
|
||||
|
||||
[CanBeNull]
|
||||
protected abstract SkinnableDrawable CreateMainPiece();
|
||||
}
|
||||
}
|
||||
|
@ -59,11 +59,10 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 350,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Height = 0.45f,
|
||||
Y = 20,
|
||||
Masking = true,
|
||||
FillMode = FillMode.Fit,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
mainContent = new Container
|
||||
|
@ -2,6 +2,8 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Taiko.Beatmaps;
|
||||
@ -19,6 +21,9 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
|
||||
public readonly IBindable<bool> LockPlayfieldAspectRange = new BindableBool(true);
|
||||
|
||||
[Resolved]
|
||||
private OsuGame? osuGame { get; set; }
|
||||
|
||||
public TaikoPlayfieldAdjustmentContainer()
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
@ -56,6 +61,18 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
relativeHeight = Math.Min(relativeHeight, 1f / 3f);
|
||||
|
||||
Scale = new Vector2(Math.Max((Parent!.ChildSize.Y / 768f) * (relativeHeight / base_relative_height), 1f));
|
||||
|
||||
// on mobile platforms where the base aspect ratio is wider, the taiko playfield
|
||||
// needs to be scaled down to remain playable.
|
||||
if (RuntimeInfo.IsMobile && osuGame != null)
|
||||
{
|
||||
const float base_aspect_ratio = 1024f / 768f;
|
||||
float gameAspectRatio = osuGame.ScalingContainerTargetDrawSize.X / osuGame.ScalingContainerTargetDrawSize.Y;
|
||||
// this magic scale is unexplainable, but required so the playfield doesn't become too zoomed out as the aspect ratio increases.
|
||||
const float magic_scale = 1.25f;
|
||||
Scale *= magic_scale * new Vector2(base_aspect_ratio / gameAspectRatio);
|
||||
}
|
||||
|
||||
Width = 1 / Scale.X;
|
||||
}
|
||||
|
||||
|
@ -372,7 +372,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
sendFrames(getPlayerIds(4), 300);
|
||||
|
||||
AddUntilStep("wait for correct track speed", () => Beatmap.Value.Track.Rate, () => Is.EqualTo(1.5));
|
||||
AddUntilStep("wait for correct track speed",
|
||||
() => this.ChildrenOfType<MultiSpectatorPlayer>().All(player => player.ClockAdjustmentsFromMods.AggregateTempo.Value == 1.5));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -11,6 +11,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
@ -52,7 +53,6 @@ using osu.Game.Tests.Resources;
|
||||
using osu.Game.Utils;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
using SharpCompress;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Navigation
|
||||
{
|
||||
|
@ -115,6 +115,8 @@ namespace osu.Game.Tests.Visual.Online
|
||||
channelList.AddChannel(createRandomPrivateChannel());
|
||||
});
|
||||
|
||||
AddStep("Add Team Channel", () => channelList.AddChannel(createRandomTeamChannel()));
|
||||
|
||||
AddStep("Add Announce Channels", () =>
|
||||
{
|
||||
for (int i = 0; i < 2; i++)
|
||||
@ -189,5 +191,16 @@ namespace osu.Game.Tests.Visual.Online
|
||||
Id = id,
|
||||
};
|
||||
}
|
||||
|
||||
private Channel createRandomTeamChannel()
|
||||
{
|
||||
int id = TestResources.GetNextTestID();
|
||||
return new Channel
|
||||
{
|
||||
Name = $"Team {id}",
|
||||
Type = ChannelType.Team,
|
||||
Id = id,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -351,7 +351,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
Id = 1,
|
||||
Name = "Collective Wangs",
|
||||
ShortName = "WANG",
|
||||
FlagUrl = "https://assets.ppy.sh/teams/logo/1/wanglogo.jpg",
|
||||
FlagUrl = "https://assets.ppy.sh/teams/flag/1/wanglogo.jpg",
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.OnlinePlay.Playlists;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
using SharpCompress;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Playlists
|
||||
{
|
||||
@ -53,7 +52,11 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
{
|
||||
AddStep("clear realm", () => Realm.Realm.Write(() => Realm.Realm.RemoveAll<BeatmapCollection>()));
|
||||
|
||||
AddStep("clear notifications", () => notificationOverlay.AllNotifications.Empty());
|
||||
AddStep("clear notifications", () =>
|
||||
{
|
||||
foreach (var notification in notificationOverlay.AllNotifications)
|
||||
notification.Close(runFlingAnimation: false);
|
||||
});
|
||||
|
||||
importBeatmap();
|
||||
|
||||
|
@ -17,6 +17,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
@ -39,6 +40,9 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
[Cached(typeof(BeatmapStore))]
|
||||
private BeatmapStore store;
|
||||
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||
|
||||
private OsuTextFlowContainer stats = null!;
|
||||
|
||||
private int beatmapCount;
|
||||
@ -94,7 +98,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Width = 500,
|
||||
Width = 800,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
},
|
||||
},
|
||||
@ -185,6 +189,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
.Where(p => ((ICarouselPanel)p).Item?.IsVisible == true)
|
||||
.OrderBy(p => p.Y)
|
||||
.ElementAt(index)
|
||||
.ChildrenOfType<PanelBase>().Single()
|
||||
.TriggerClick();
|
||||
});
|
||||
}
|
||||
|
@ -28,42 +28,42 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
[Test]
|
||||
public void TestOpenCloseGroupWithNoSelectionMouse()
|
||||
{
|
||||
AddAssert("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
AddUntilStep("no sets visible", () => Carousel.ChildrenOfType<BeatmapSetPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
AddAssert("no beatmaps visible", () => Carousel.ChildrenOfType<PanelBeatmap>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
AddUntilStep("no sets visible", () => Carousel.ChildrenOfType<PanelBeatmapSet>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
CheckNoSelection();
|
||||
|
||||
ClickVisiblePanel<GroupPanel>(0);
|
||||
ClickVisiblePanel<PanelGroup>(0);
|
||||
|
||||
AddUntilStep("some sets visible", () => Carousel.ChildrenOfType<BeatmapSetPanel>().Count(p => p.Alpha > 0), () => Is.GreaterThan(0));
|
||||
AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
AddUntilStep("some sets visible", () => Carousel.ChildrenOfType<PanelBeatmapSet>().Count(p => p.Alpha > 0), () => Is.GreaterThan(0));
|
||||
AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType<PanelBeatmap>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
CheckNoSelection();
|
||||
|
||||
ClickVisiblePanel<GroupPanel>(0);
|
||||
ClickVisiblePanel<PanelGroup>(0);
|
||||
|
||||
AddUntilStep("no sets visible", () => Carousel.ChildrenOfType<BeatmapSetPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
AddUntilStep("no sets visible", () => Carousel.ChildrenOfType<PanelBeatmapSet>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType<PanelBeatmap>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
CheckNoSelection();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOpenCloseGroupWithNoSelectionKeyboard()
|
||||
{
|
||||
AddAssert("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
AddUntilStep("no sets visible", () => Carousel.ChildrenOfType<BeatmapSetPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
AddAssert("no beatmaps visible", () => Carousel.ChildrenOfType<PanelBeatmap>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
AddUntilStep("no sets visible", () => Carousel.ChildrenOfType<PanelBeatmapSet>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
CheckNoSelection();
|
||||
|
||||
SelectNextPanel();
|
||||
Select();
|
||||
|
||||
AddUntilStep("some sets visible", () => Carousel.ChildrenOfType<BeatmapSetPanel>().Count(p => p.Alpha > 0), () => Is.GreaterThan(0));
|
||||
AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
AddUntilStep("some sets visible", () => Carousel.ChildrenOfType<PanelBeatmapSet>().Count(p => p.Alpha > 0), () => Is.GreaterThan(0));
|
||||
AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType<PanelBeatmap>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
AddAssert("keyboard selected is expanded", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.True);
|
||||
CheckNoSelection();
|
||||
|
||||
Select();
|
||||
|
||||
AddUntilStep("no sets visible", () => Carousel.ChildrenOfType<BeatmapSetPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
AddUntilStep("no sets visible", () => Carousel.ChildrenOfType<PanelBeatmapSet>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType<PanelBeatmap>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
AddAssert("keyboard selected is collapsed", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.False);
|
||||
CheckNoSelection();
|
||||
}
|
||||
@ -96,10 +96,10 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
AddUntilStep("drawable selection restored", () => GetSelectedPanel()?.Item?.Model, () => Is.EqualTo(selection));
|
||||
AddAssert("carousel item is visible", () => GetSelectedPanel()?.Item?.IsVisible, () => Is.True);
|
||||
|
||||
ClickVisiblePanel<GroupPanel>(0);
|
||||
ClickVisiblePanel<PanelGroup>(0);
|
||||
AddUntilStep("carousel item not visible", GetSelectedPanel, () => Is.Null);
|
||||
|
||||
ClickVisiblePanel<GroupPanel>(0);
|
||||
ClickVisiblePanel<PanelGroup>(0);
|
||||
AddUntilStep("carousel item is visible", () => GetSelectedPanel()?.Item?.IsVisible, () => Is.True);
|
||||
}
|
||||
|
||||
@ -137,7 +137,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
// open first group
|
||||
Select();
|
||||
CheckNoSelection();
|
||||
AddUntilStep("some beatmaps visible", () => Carousel.ChildrenOfType<BeatmapSetPanel>().Count(p => p.Alpha > 0), () => Is.GreaterThan(0));
|
||||
AddUntilStep("some beatmaps visible", () => Carousel.ChildrenOfType<PanelBeatmapSet>().Count(p => p.Alpha > 0), () => Is.GreaterThan(0));
|
||||
|
||||
SelectNextPanel();
|
||||
Select();
|
||||
|
@ -29,32 +29,32 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
[Test]
|
||||
public void TestOpenCloseGroupWithNoSelectionMouse()
|
||||
{
|
||||
AddAssert("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
AddAssert("no beatmaps visible", () => Carousel.ChildrenOfType<PanelBeatmap>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
CheckNoSelection();
|
||||
|
||||
ClickVisiblePanel<GroupPanel>(0);
|
||||
AddUntilStep("some beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.GreaterThan(0));
|
||||
ClickVisiblePanel<PanelGroup>(0);
|
||||
AddUntilStep("some beatmaps visible", () => Carousel.ChildrenOfType<PanelBeatmap>().Count(p => p.Alpha > 0), () => Is.GreaterThan(0));
|
||||
CheckNoSelection();
|
||||
|
||||
ClickVisiblePanel<GroupPanel>(0);
|
||||
AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
ClickVisiblePanel<PanelGroup>(0);
|
||||
AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType<PanelBeatmap>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
CheckNoSelection();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOpenCloseGroupWithNoSelectionKeyboard()
|
||||
{
|
||||
AddAssert("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
AddAssert("no beatmaps visible", () => Carousel.ChildrenOfType<PanelBeatmap>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
CheckNoSelection();
|
||||
|
||||
SelectNextPanel();
|
||||
Select();
|
||||
AddUntilStep("some beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.GreaterThan(0));
|
||||
AddUntilStep("some beatmaps visible", () => Carousel.ChildrenOfType<PanelBeatmap>().Count(p => p.Alpha > 0), () => Is.GreaterThan(0));
|
||||
AddAssert("keyboard selected is expanded", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.True);
|
||||
CheckNoSelection();
|
||||
|
||||
Select();
|
||||
AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType<PanelBeatmap>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
AddAssert("keyboard selected is collapsed", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.False);
|
||||
CheckNoSelection();
|
||||
}
|
||||
@ -87,10 +87,10 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
AddUntilStep("drawable selection restored", () => GetSelectedPanel()?.Item?.Model, () => Is.EqualTo(selection));
|
||||
AddAssert("carousel item is visible", () => GetSelectedPanel()?.Item?.IsVisible, () => Is.True);
|
||||
|
||||
ClickVisiblePanel<GroupPanel>(0);
|
||||
ClickVisiblePanel<PanelGroup>(0);
|
||||
AddUntilStep("carousel item not visible", GetSelectedPanel, () => Is.Null);
|
||||
|
||||
ClickVisiblePanel<GroupPanel>(0);
|
||||
ClickVisiblePanel<PanelGroup>(0);
|
||||
AddUntilStep("carousel item is visible", () => GetSelectedPanel()?.Item?.IsVisible, () => Is.True);
|
||||
}
|
||||
|
||||
@ -120,18 +120,18 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
SelectNextGroup();
|
||||
WaitForGroupSelection(0, 0);
|
||||
|
||||
AddAssert("keyboard selected panel is beatmap", GetKeyboardSelectedPanel, Is.TypeOf<BeatmapPanel>);
|
||||
AddAssert("selected panel is beatmap", GetSelectedPanel, Is.TypeOf<BeatmapPanel>);
|
||||
AddAssert("keyboard selected panel is beatmap", GetKeyboardSelectedPanel, Is.TypeOf<PanelBeatmap>);
|
||||
AddAssert("selected panel is beatmap", GetSelectedPanel, Is.TypeOf<PanelBeatmap>);
|
||||
|
||||
ClickVisiblePanel<GroupPanel>(0);
|
||||
AddAssert("keyboard selected panel is group", GetKeyboardSelectedPanel, Is.TypeOf<GroupPanel>);
|
||||
ClickVisiblePanel<PanelGroup>(0);
|
||||
AddAssert("keyboard selected panel is group", GetKeyboardSelectedPanel, Is.TypeOf<PanelGroup>);
|
||||
AddAssert("keyboard selected panel is contracted", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.False);
|
||||
|
||||
ClickVisiblePanel<GroupPanel>(0);
|
||||
AddAssert("keyboard selected panel is group", GetKeyboardSelectedPanel, Is.TypeOf<GroupPanel>);
|
||||
ClickVisiblePanel<PanelGroup>(0);
|
||||
AddAssert("keyboard selected panel is group", GetKeyboardSelectedPanel, Is.TypeOf<PanelGroup>);
|
||||
AddAssert("keyboard selected panel is expanded", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.True);
|
||||
|
||||
AddAssert("selected panel is still beatmap", GetSelectedPanel, Is.TypeOf<BeatmapPanel>);
|
||||
AddAssert("selected panel is still beatmap", GetSelectedPanel, Is.TypeOf<PanelBeatmap>);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -146,7 +146,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
// open first group
|
||||
Select();
|
||||
CheckNoSelection();
|
||||
AddUntilStep("some beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.GreaterThan(0));
|
||||
AddUntilStep("some beatmaps visible", () => Carousel.ChildrenOfType<PanelBeatmap>().Count(p => p.Alpha > 0), () => Is.GreaterThan(0));
|
||||
|
||||
SelectNextPanel();
|
||||
Select();
|
||||
@ -171,23 +171,23 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
[Test]
|
||||
public void TestInputHandlingWithinGaps()
|
||||
{
|
||||
AddAssert("no beatmaps visible", () => !GetVisiblePanels<BeatmapPanel>().Any());
|
||||
AddAssert("no beatmaps visible", () => !GetVisiblePanels<PanelBeatmap>().Any());
|
||||
|
||||
// Clicks just above the first group panel should not actuate any action.
|
||||
ClickVisiblePanelWithOffset<GroupPanel>(0, new Vector2(0, -(GroupPanel.HEIGHT / 2 + 1)));
|
||||
ClickVisiblePanelWithOffset<PanelGroup>(0, new Vector2(0, -(PanelGroup.HEIGHT / 2 + 1)));
|
||||
|
||||
AddAssert("no beatmaps visible", () => !GetVisiblePanels<BeatmapPanel>().Any());
|
||||
AddAssert("no beatmaps visible", () => !GetVisiblePanels<PanelBeatmap>().Any());
|
||||
|
||||
ClickVisiblePanelWithOffset<GroupPanel>(0, new Vector2(0, -(GroupPanel.HEIGHT / 2)));
|
||||
ClickVisiblePanelWithOffset<PanelGroup>(0, new Vector2(0, -(PanelGroup.HEIGHT / 2)));
|
||||
|
||||
AddUntilStep("wait for beatmaps visible", () => GetVisiblePanels<BeatmapPanel>().Any());
|
||||
AddUntilStep("wait for beatmaps visible", () => GetVisiblePanels<PanelBeatmap>().Any());
|
||||
CheckNoSelection();
|
||||
|
||||
// Beatmap panels expand their selection area to cover holes from spacing.
|
||||
ClickVisiblePanelWithOffset<BeatmapPanel>(0, new Vector2(0, -(CarouselItem.DEFAULT_HEIGHT / 2 + 1)));
|
||||
ClickVisiblePanelWithOffset<PanelBeatmap>(0, new Vector2(0, -(CarouselItem.DEFAULT_HEIGHT / 2 + 1)));
|
||||
WaitForGroupSelection(0, 0);
|
||||
|
||||
ClickVisiblePanelWithOffset<BeatmapPanel>(1, new Vector2(0, (CarouselItem.DEFAULT_HEIGHT / 2 + 1)));
|
||||
ClickVisiblePanelWithOffset<PanelBeatmap>(1, new Vector2(0, (CarouselItem.DEFAULT_HEIGHT / 2 + 1)));
|
||||
WaitForGroupSelection(0, 1);
|
||||
}
|
||||
}
|
||||
|
@ -213,27 +213,27 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
AddBeatmaps(2, 5);
|
||||
WaitForDrawablePanels();
|
||||
|
||||
AddAssert("no beatmaps visible", () => !GetVisiblePanels<BeatmapPanel>().Any());
|
||||
AddAssert("no beatmaps visible", () => !GetVisiblePanels<PanelBeatmap>().Any());
|
||||
|
||||
// Clicks just above the first group panel should not actuate any action.
|
||||
ClickVisiblePanelWithOffset<BeatmapSetPanel>(0, new Vector2(0, -(BeatmapSetPanel.HEIGHT / 2 + 1)));
|
||||
ClickVisiblePanelWithOffset<PanelBeatmapSet>(0, new Vector2(0, -(PanelBeatmapSet.HEIGHT / 2 + 1)));
|
||||
|
||||
AddAssert("no beatmaps visible", () => !GetVisiblePanels<BeatmapPanel>().Any());
|
||||
AddAssert("no beatmaps visible", () => !GetVisiblePanels<PanelBeatmap>().Any());
|
||||
|
||||
ClickVisiblePanelWithOffset<BeatmapSetPanel>(0, new Vector2(0, -(BeatmapSetPanel.HEIGHT / 2)));
|
||||
ClickVisiblePanelWithOffset<PanelBeatmapSet>(0, new Vector2(0, -(PanelBeatmapSet.HEIGHT / 2)));
|
||||
|
||||
AddUntilStep("wait for beatmaps visible", () => GetVisiblePanels<BeatmapPanel>().Any());
|
||||
AddUntilStep("wait for beatmaps visible", () => GetVisiblePanels<PanelBeatmap>().Any());
|
||||
WaitForSelection(0, 0);
|
||||
|
||||
// Beatmap panels expand their selection area to cover holes from spacing.
|
||||
ClickVisiblePanelWithOffset<BeatmapPanel>(1, new Vector2(0, -(CarouselItem.DEFAULT_HEIGHT / 2 + 1)));
|
||||
ClickVisiblePanelWithOffset<PanelBeatmap>(1, new Vector2(0, -(CarouselItem.DEFAULT_HEIGHT / 2 + 1)));
|
||||
WaitForSelection(0, 0);
|
||||
|
||||
// Panels with higher depth will handle clicks in the gutters for simplicity.
|
||||
ClickVisiblePanelWithOffset<BeatmapPanel>(2, new Vector2(0, (CarouselItem.DEFAULT_HEIGHT / 2 + 1)));
|
||||
ClickVisiblePanelWithOffset<PanelBeatmap>(2, new Vector2(0, (CarouselItem.DEFAULT_HEIGHT / 2 + 1)));
|
||||
WaitForSelection(0, 2);
|
||||
|
||||
ClickVisiblePanelWithOffset<BeatmapPanel>(3, new Vector2(0, (CarouselItem.DEFAULT_HEIGHT / 2 + 1)));
|
||||
ClickVisiblePanelWithOffset<PanelBeatmap>(3, new Vector2(0, (CarouselItem.DEFAULT_HEIGHT / 2 + 1)));
|
||||
WaitForSelection(0, 3);
|
||||
}
|
||||
|
||||
|
@ -30,16 +30,16 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
Quad positionBefore = default;
|
||||
|
||||
AddStep("select middle beatmap", () => Carousel.CurrentSelection = BeatmapSets.ElementAt(BeatmapSets.Count - 2).Beatmaps.First());
|
||||
AddStep("scroll to selected item", () => Scroll.ScrollTo(Scroll.ChildrenOfType<BeatmapPanel>().Single(p => p.Selected.Value)));
|
||||
AddStep("scroll to selected item", () => Scroll.ScrollTo(Scroll.ChildrenOfType<PanelBeatmap>().Single(p => p.Selected.Value)));
|
||||
|
||||
WaitForScrolling();
|
||||
|
||||
AddStep("save selected screen position", () => positionBefore = Carousel.ChildrenOfType<BeatmapPanel>().FirstOrDefault(p => p.Selected.Value)!.ScreenSpaceDrawQuad);
|
||||
AddStep("save selected screen position", () => positionBefore = Carousel.ChildrenOfType<PanelBeatmap>().FirstOrDefault(p => p.Selected.Value)!.ScreenSpaceDrawQuad);
|
||||
|
||||
RemoveFirstBeatmap();
|
||||
WaitForSorting();
|
||||
|
||||
AddAssert("select screen position unchanged", () => Carousel.ChildrenOfType<BeatmapPanel>().Single(p => p.Selected.Value).ScreenSpaceDrawQuad,
|
||||
AddAssert("select screen position unchanged", () => Carousel.ChildrenOfType<PanelBeatmap>().Single(p => p.Selected.Value).ScreenSpaceDrawQuad,
|
||||
() => Is.EqualTo(positionBefore));
|
||||
}
|
||||
|
||||
@ -54,11 +54,11 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
WaitForScrolling();
|
||||
|
||||
AddStep("save selected screen position", () => positionBefore = Carousel.ChildrenOfType<BeatmapPanel>().FirstOrDefault(p => p.Selected.Value)!.ScreenSpaceDrawQuad);
|
||||
AddStep("save selected screen position", () => positionBefore = Carousel.ChildrenOfType<PanelBeatmap>().FirstOrDefault(p => p.Selected.Value)!.ScreenSpaceDrawQuad);
|
||||
|
||||
RemoveFirstBeatmap();
|
||||
WaitForSorting();
|
||||
AddAssert("select screen position unchanged", () => Carousel.ChildrenOfType<BeatmapPanel>().Single(p => p.Selected.Value).ScreenSpaceDrawQuad,
|
||||
AddAssert("select screen position unchanged", () => Carousel.ChildrenOfType<PanelBeatmap>().Single(p => p.Selected.Value).ScreenSpaceDrawQuad,
|
||||
() => Is.EqualTo(positionBefore));
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,105 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osu.Game.Tests.Visual.UserInterface;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public partial class TestSceneBeatmapCarouselV2DifficultyPanel : ThemeComparisonTestScene
|
||||
{
|
||||
[Resolved]
|
||||
private BeatmapManager beatmaps { get; set; } = null!;
|
||||
|
||||
private BeatmapInfo beatmap = null!;
|
||||
|
||||
public TestSceneBeatmapCarouselV2DifficultyPanel()
|
||||
: base(false)
|
||||
{
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
var beatmapSet = beatmaps.GetAllUsableBeatmapSets().FirstOrDefault(b => b.OnlineID == 241526)
|
||||
?? beatmaps.GetAllUsableBeatmapSets().FirstOrDefault(b => !b.Protected)
|
||||
?? TestResources.CreateTestBeatmapSetInfo();
|
||||
|
||||
beatmap = beatmapSet.Beatmaps.First();
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestDisplay()
|
||||
{
|
||||
AddStep("display", () => CreateThemedContent(OverlayColourScheme.Aquamarine));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRandomBeatmap()
|
||||
{
|
||||
AddStep("random beatmap", () =>
|
||||
{
|
||||
var randomSet = beatmaps.GetAllUsableBeatmapSets().MinBy(_ => RNG.Next());
|
||||
randomSet ??= TestResources.CreateTestBeatmapSetInfo();
|
||||
beatmap = randomSet.Beatmaps.MinBy(_ => RNG.Next())!;
|
||||
|
||||
CreateThemedContent(OverlayColourScheme.Aquamarine);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestManiaRuleset()
|
||||
{
|
||||
AddToggleStep("mania ruleset", v => Ruleset.Value = v ? new ManiaRuleset().RulesetInfo : new OsuRuleset().RulesetInfo);
|
||||
}
|
||||
|
||||
protected override Drawable CreateContent()
|
||||
{
|
||||
return new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Width = 0.5f,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0f, 5f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new PanelBeatmap
|
||||
{
|
||||
Item = new CarouselItem(beatmap)
|
||||
},
|
||||
new PanelBeatmap
|
||||
{
|
||||
Item = new CarouselItem(beatmap),
|
||||
KeyboardSelected = { Value = true }
|
||||
},
|
||||
new PanelBeatmap
|
||||
{
|
||||
Item = new CarouselItem(beatmap),
|
||||
Selected = { Value = true }
|
||||
},
|
||||
new PanelBeatmap
|
||||
{
|
||||
Item = new CarouselItem(beatmap),
|
||||
KeyboardSelected = { Value = true },
|
||||
Selected = { Value = true }
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
// 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.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Tests.Visual.UserInterface;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public partial class TestSceneBeatmapCarouselV2GroupPanel : ThemeComparisonTestScene
|
||||
{
|
||||
public TestSceneBeatmapCarouselV2GroupPanel()
|
||||
: base(false)
|
||||
{
|
||||
}
|
||||
|
||||
protected override Drawable CreateContent()
|
||||
{
|
||||
return new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Width = 0.5f,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0f, 5f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new PanelGroup
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition('A', "Group A"))
|
||||
},
|
||||
new PanelGroup
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition('A', "Group A")),
|
||||
KeyboardSelected = { Value = true }
|
||||
},
|
||||
new PanelGroup
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition('A', "Group A")),
|
||||
Expanded = { Value = true }
|
||||
},
|
||||
new PanelGroup
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition('A', "Group A")),
|
||||
KeyboardSelected = { Value = true },
|
||||
Expanded = { Value = true }
|
||||
},
|
||||
new PanelGroupStarDifficulty
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition(1, "1"))
|
||||
},
|
||||
new PanelGroupStarDifficulty
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition(3, "3")),
|
||||
Expanded = { Value = true }
|
||||
},
|
||||
new PanelGroupStarDifficulty
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition(5, "5")),
|
||||
},
|
||||
new PanelGroupStarDifficulty
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition(7, "7")),
|
||||
Expanded = { Value = true }
|
||||
},
|
||||
new PanelGroupStarDifficulty
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition(8, "8")),
|
||||
},
|
||||
new PanelGroupStarDifficulty
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition(9, "9")),
|
||||
Expanded = { Value = true }
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osu.Game.Tests.Visual.UserInterface;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public partial class TestSceneBeatmapCarouselV2SetPanel : ThemeComparisonTestScene
|
||||
{
|
||||
[Resolved]
|
||||
private BeatmapManager beatmaps { get; set; } = null!;
|
||||
|
||||
private BeatmapSetInfo beatmapSet = null!;
|
||||
|
||||
public TestSceneBeatmapCarouselV2SetPanel()
|
||||
: base(false)
|
||||
{
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
beatmapSet = beatmaps.GetAllUsableBeatmapSets().FirstOrDefault(b => b.OnlineID == 241526)
|
||||
?? beatmaps.GetAllUsableBeatmapSets().FirstOrDefault(b => !b.Protected)
|
||||
?? TestResources.CreateTestBeatmapSetInfo();
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestDisplay()
|
||||
{
|
||||
AddStep("display", () => CreateThemedContent(OverlayColourScheme.Aquamarine));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRandomBeatmap()
|
||||
{
|
||||
AddStep("random beatmap", () =>
|
||||
{
|
||||
var randomSet = beatmaps.GetAllUsableBeatmapSets().MinBy(_ => RNG.Next());
|
||||
randomSet ??= TestResources.CreateTestBeatmapSetInfo();
|
||||
beatmapSet = randomSet;
|
||||
|
||||
CreateThemedContent(OverlayColourScheme.Aquamarine);
|
||||
});
|
||||
}
|
||||
|
||||
protected override Drawable CreateContent()
|
||||
{
|
||||
return new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Width = 0.5f,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0f, 5f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new PanelBeatmapSet
|
||||
{
|
||||
Item = new CarouselItem(beatmapSet)
|
||||
},
|
||||
new PanelBeatmapSet
|
||||
{
|
||||
Item = new CarouselItem(beatmapSet),
|
||||
KeyboardSelected = { Value = true }
|
||||
},
|
||||
new PanelBeatmapSet
|
||||
{
|
||||
Item = new CarouselItem(beatmapSet),
|
||||
Expanded = { Value = true }
|
||||
},
|
||||
new PanelBeatmapSet
|
||||
{
|
||||
Item = new CarouselItem(beatmapSet),
|
||||
KeyboardSelected = { Value = true },
|
||||
Expanded = { Value = true }
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osu.Game.Tests.Visual.UserInterface;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public partial class TestSceneBeatmapCarouselV2StandalonePanel : ThemeComparisonTestScene
|
||||
{
|
||||
[Resolved]
|
||||
private BeatmapManager beatmaps { get; set; } = null!;
|
||||
|
||||
private BeatmapInfo beatmap = null!;
|
||||
|
||||
public TestSceneBeatmapCarouselV2StandalonePanel()
|
||||
: base(false)
|
||||
{
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
var beatmapSet = beatmaps.GetAllUsableBeatmapSets().FirstOrDefault(b => b.OnlineID == 241526)
|
||||
?? beatmaps.GetAllUsableBeatmapSets().FirstOrDefault(b => !b.Protected)
|
||||
?? TestResources.CreateTestBeatmapSetInfo();
|
||||
|
||||
beatmap = beatmapSet.Beatmaps.First();
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestDisplay()
|
||||
{
|
||||
AddStep("display", () => CreateThemedContent(OverlayColourScheme.Aquamarine));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRandomBeatmap()
|
||||
{
|
||||
AddStep("random beatmap", () =>
|
||||
{
|
||||
var randomSet = beatmaps.GetAllUsableBeatmapSets().MinBy(_ => RNG.Next());
|
||||
randomSet ??= TestResources.CreateTestBeatmapSetInfo();
|
||||
beatmap = randomSet.Beatmaps.MinBy(_ => RNG.Next())!;
|
||||
|
||||
CreateThemedContent(OverlayColourScheme.Aquamarine);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestManiaRuleset()
|
||||
{
|
||||
AddToggleStep("mania ruleset", v => Ruleset.Value = v ? new ManiaRuleset().RulesetInfo : new OsuRuleset().RulesetInfo);
|
||||
}
|
||||
|
||||
protected override Drawable CreateContent()
|
||||
{
|
||||
return new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Width = 0.5f,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0f, 5f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new PanelBeatmapStandalone
|
||||
{
|
||||
Item = new CarouselItem(beatmap)
|
||||
},
|
||||
new PanelBeatmapStandalone
|
||||
{
|
||||
Item = new CarouselItem(beatmap),
|
||||
KeyboardSelected = { Value = true }
|
||||
},
|
||||
new PanelBeatmapStandalone
|
||||
{
|
||||
Item = new CarouselItem(beatmap),
|
||||
Selected = { Value = true }
|
||||
},
|
||||
new PanelBeatmapStandalone
|
||||
{
|
||||
Item = new CarouselItem(beatmap),
|
||||
KeyboardSelected = { Value = true },
|
||||
Selected = { Value = true }
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -9,15 +9,27 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Catch;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Taiko;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.Footer;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Screens.SelectV2.Footer;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
@ -30,6 +42,10 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
[Cached]
|
||||
private readonly OsuLogo logo;
|
||||
|
||||
private BeatmapManager beatmapManager = null!;
|
||||
|
||||
protected override bool UseOnlineAPI => true;
|
||||
|
||||
public TestSceneSongSelect()
|
||||
{
|
||||
Children = new Drawable[]
|
||||
@ -49,6 +65,35 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, IAPIProvider onlineAPI)
|
||||
{
|
||||
BeatmapStore beatmapStore;
|
||||
BeatmapUpdater beatmapUpdater;
|
||||
BeatmapDifficultyCache difficultyCache;
|
||||
|
||||
// These DI caches are required to ensure for interactive runs this test scene doesn't nuke all user beatmaps in the local install.
|
||||
// At a point we have isolated interactive test runs enough, this can likely be removed.
|
||||
Dependencies.Cache(new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(Realm);
|
||||
Dependencies.Cache(difficultyCache = new BeatmapDifficultyCache());
|
||||
Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, onlineAPI, Audio, Resources, host, Beatmap.Default, difficultyCache));
|
||||
Dependencies.CacheAs(beatmapUpdater = new BeatmapUpdater(beatmapManager, difficultyCache, onlineAPI, LocalStorage));
|
||||
Dependencies.CacheAs(beatmapStore = new RealmDetachedBeatmapStore());
|
||||
|
||||
beatmapManager.ProcessBeatmap = (set, scope) => beatmapUpdater.Process(set, scope);
|
||||
|
||||
MusicController music;
|
||||
Dependencies.Cache(music = new MusicController());
|
||||
|
||||
// required to get bindables attached
|
||||
Add(difficultyCache);
|
||||
Add(music);
|
||||
Add(beatmapStore);
|
||||
|
||||
Dependencies.Cache(new OsuConfigManager(LocalStorage));
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
@ -64,6 +109,16 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
|
||||
AddStep("load screen", () => Stack.Push(new Screens.SelectV2.SongSelectV2()));
|
||||
AddUntilStep("wait for load", () => Stack.CurrentScreen is Screens.SelectV2.SongSelectV2 songSelect && songSelect.IsLoaded);
|
||||
AddStep("import test beatmap", () => beatmapManager.Import(TestResources.GetTestBeatmapForImport()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRulesets()
|
||||
{
|
||||
AddStep("set osu ruleset", () => Ruleset.Value = new OsuRuleset().RulesetInfo);
|
||||
AddStep("set taiko ruleset", () => Ruleset.Value = new TaikoRuleset().RulesetInfo);
|
||||
AddStep("set catch ruleset", () => Ruleset.Value = new CatchRuleset().RulesetInfo);
|
||||
AddStep("set mania ruleset", () => Ruleset.Value = new ManiaRuleset().RulesetInfo);
|
||||
}
|
||||
|
||||
#region Footer
|
||||
@ -80,8 +135,11 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
AddStep("modified", () => SelectedMods.Value = new List<Mod> { new OsuModDoubleTime { SpeedChange = { Value = 1.2 } } });
|
||||
AddStep("modified + one", () => SelectedMods.Value = new List<Mod> { new OsuModHidden(), new OsuModDoubleTime { SpeedChange = { Value = 1.2 } } });
|
||||
AddStep("modified + two", () => SelectedMods.Value = new List<Mod> { new OsuModHidden(), new OsuModHardRock(), new OsuModDoubleTime { SpeedChange = { Value = 1.2 } } });
|
||||
AddStep("modified + three", () => SelectedMods.Value = new List<Mod> { new OsuModHidden(), new OsuModHardRock(), new OsuModClassic(), new OsuModDoubleTime { SpeedChange = { Value = 1.2 } } });
|
||||
AddStep("modified + four", () => SelectedMods.Value = new List<Mod> { new OsuModHidden(), new OsuModHardRock(), new OsuModClassic(), new OsuModDifficultyAdjust(), new OsuModDoubleTime { SpeedChange = { Value = 1.2 } } });
|
||||
AddStep("modified + three",
|
||||
() => SelectedMods.Value = new List<Mod> { new OsuModHidden(), new OsuModHardRock(), new OsuModClassic(), new OsuModDoubleTime { SpeedChange = { Value = 1.2 } } });
|
||||
AddStep("modified + four",
|
||||
() => SelectedMods.Value = new List<Mod>
|
||||
{ new OsuModHidden(), new OsuModHardRock(), new OsuModClassic(), new OsuModDifficultyAdjust(), new OsuModDoubleTime { SpeedChange = { Value = 1.2 } } });
|
||||
|
||||
AddStep("clear mods", () => SelectedMods.Value = Array.Empty<Mod>());
|
||||
AddWaitStep("wait", 3);
|
||||
|
@ -0,0 +1,62 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public partial class TestSceneUpdateBeatmapSetButtonV2 : OsuTestScene
|
||||
{
|
||||
private UpdateBeatmapSetButton button = null!;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
Child = button = new UpdateBeatmapSetButton
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
};
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestNullBeatmap()
|
||||
{
|
||||
AddStep("null beatmap", () => button.BeatmapSet = null);
|
||||
AddAssert("button invisible", () => button.Alpha == 0f);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestUpdatedBeatmap()
|
||||
{
|
||||
AddStep("updated beatmap", () => button.BeatmapSet = new BeatmapSetInfo
|
||||
{
|
||||
Beatmaps = { new BeatmapInfo() }
|
||||
});
|
||||
AddAssert("button invisible", () => button.Alpha == 0f);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNonUpdatedBeatmap()
|
||||
{
|
||||
AddStep("non-updated beatmap", () => button.BeatmapSet = new BeatmapSetInfo
|
||||
{
|
||||
Beatmaps =
|
||||
{
|
||||
new BeatmapInfo
|
||||
{
|
||||
MD5Hash = "test",
|
||||
OnlineMD5Hash = "online",
|
||||
LastOnlineUpdate = DateTimeOffset.Now,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
AddAssert("button visible", () => button.Alpha == 1f);
|
||||
}
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
@ -220,6 +221,29 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
checkBindableAtValue("Circle Size", null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestResetToDefaultViaDoubleClickingNub()
|
||||
{
|
||||
setBeatmapWithDifficultyParameters(5);
|
||||
|
||||
setSliderValue("Circle Size", 3);
|
||||
setExtendedLimits(true);
|
||||
|
||||
checkSliderAtValue("Circle Size", 3);
|
||||
checkBindableAtValue("Circle Size", 3);
|
||||
|
||||
AddStep("double click circle size nub", () =>
|
||||
{
|
||||
var nub = this.ChildrenOfType<RoundedSliderBar<float>.SliderNub>().First();
|
||||
InputManager.MoveMouseTo(nub);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
checkSliderAtValue("Circle Size", 5);
|
||||
checkBindableAtValue("Circle Size", null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestModSettingChangeTracker()
|
||||
{
|
||||
|
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
|
||||
<PackageReference Include="DeepEqual" Version="4.2.1" />
|
||||
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
|
||||
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||
|
@ -4,7 +4,7 @@
|
||||
<StartupObject>osu.Game.Tournament.Tests.TournamentTestRunner</StartupObject>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
|
||||
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
</ItemGroup>
|
||||
|
@ -6,6 +6,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Tournament.Models;
|
||||
@ -35,12 +36,20 @@ namespace osu.Game.Tournament.Components
|
||||
Size = new Vector2(75, 54);
|
||||
Masking = true;
|
||||
CornerRadius = 5;
|
||||
Child = flagSprite = new Sprite
|
||||
Children = new Drawable[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
FillMode = FillMode.Fill
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Colour4.FromHex("333"),
|
||||
},
|
||||
flagSprite = new Sprite
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
FillMode = FillMode.Fit
|
||||
},
|
||||
};
|
||||
|
||||
(flag = team.FlagName.GetBoundCopy()).BindValueChanged(_ => flagSprite.Texture = textures.Get($@"Flags/{team.FlagName}"), true);
|
||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
dotSize = value;
|
||||
|
||||
if (IsLoaded)
|
||||
updateDotDimensions();
|
||||
updateDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,13 +42,27 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
dotSpacing = value;
|
||||
|
||||
if (IsLoaded)
|
||||
updateDotDimensions();
|
||||
updateDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
private IBeatmapSetInfo? beatmapSet;
|
||||
|
||||
public IBeatmapSetInfo? BeatmapSet
|
||||
{
|
||||
get => beatmapSet;
|
||||
set
|
||||
{
|
||||
beatmapSet = value;
|
||||
|
||||
if (IsLoaded)
|
||||
updateDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly FillFlowContainer<RulesetDifficultyGroup> flow;
|
||||
|
||||
public DifficultySpectrumDisplay(IBeatmapSetInfo beatmapSet)
|
||||
public DifficultySpectrumDisplay(IBeatmapSetInfo? beatmapSet = null)
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
@ -59,25 +73,31 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
Direction = FillDirection.Horizontal,
|
||||
};
|
||||
|
||||
// matching web: https://github.com/ppy/osu-web/blob/d06d8c5e735eb1f48799b1654b528e9a7afb0a35/resources/assets/lib/beatmapset-panel.tsx#L127
|
||||
bool collapsed = beatmapSet.Beatmaps.Count() > 12;
|
||||
|
||||
foreach (var rulesetGrouping in beatmapSet.Beatmaps.GroupBy(beatmap => beatmap.Ruleset).OrderBy(group => group.Key))
|
||||
flow.Add(new RulesetDifficultyGroup(rulesetGrouping.Key.OnlineID, rulesetGrouping, collapsed));
|
||||
BeatmapSet = beatmapSet;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
updateDotDimensions();
|
||||
updateDisplay();
|
||||
}
|
||||
|
||||
private void updateDotDimensions()
|
||||
private void updateDisplay()
|
||||
{
|
||||
foreach (var group in flow)
|
||||
flow.Clear();
|
||||
|
||||
if (beatmapSet == null)
|
||||
return;
|
||||
|
||||
// matching web: https://github.com/ppy/osu-web/blob/d06d8c5e735eb1f48799b1654b528e9a7afb0a35/resources/assets/lib/beatmapset-panel.tsx#L127
|
||||
bool collapsed = beatmapSet.Beatmaps.Count() > 12;
|
||||
|
||||
foreach (var rulesetGrouping in beatmapSet.Beatmaps.GroupBy(beatmap => beatmap.Ruleset).OrderBy(group => group.Key))
|
||||
{
|
||||
group.DotSize = DotSize;
|
||||
group.DotSpacing = DotSpacing;
|
||||
flow.Add(new RulesetDifficultyGroup(rulesetGrouping.Key.OnlineID, rulesetGrouping, collapsed, dotSize)
|
||||
{
|
||||
Spacing = new Vector2(DotSpacing, 0f),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,26 +106,14 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
private readonly int rulesetId;
|
||||
private readonly IEnumerable<IBeatmapInfo> beatmapInfos;
|
||||
private readonly bool collapsed;
|
||||
private readonly Vector2 dotSize;
|
||||
|
||||
public RulesetDifficultyGroup(int rulesetId, IEnumerable<IBeatmapInfo> beatmapInfos, bool collapsed)
|
||||
public RulesetDifficultyGroup(int rulesetId, IEnumerable<IBeatmapInfo> beatmapInfos, bool collapsed, Vector2 dotSize)
|
||||
{
|
||||
this.rulesetId = rulesetId;
|
||||
this.beatmapInfos = beatmapInfos;
|
||||
this.collapsed = collapsed;
|
||||
}
|
||||
|
||||
public Vector2 DotSize
|
||||
{
|
||||
set
|
||||
{
|
||||
foreach (var dot in Children.OfType<DifficultyDot>())
|
||||
dot.Size = value;
|
||||
}
|
||||
}
|
||||
|
||||
public float DotSpacing
|
||||
{
|
||||
set => Spacing = new Vector2(value, 0);
|
||||
this.dotSize = dotSize;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -125,7 +133,7 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
if (!collapsed)
|
||||
{
|
||||
foreach (var beatmapInfo in beatmapInfos.OrderBy(bi => bi.StarRating))
|
||||
Add(new DifficultyDot(beatmapInfo.StarRating));
|
||||
Add(new DifficultyDot(beatmapInfo.StarRating, dotSize));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -145,9 +153,10 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
{
|
||||
private readonly double starDifficulty;
|
||||
|
||||
public DifficultyDot(double starDifficulty)
|
||||
public DifficultyDot(double starDifficulty, Vector2 dotSize)
|
||||
{
|
||||
this.starDifficulty = starDifficulty;
|
||||
Size = dotSize;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
|
@ -73,6 +73,16 @@ namespace osu.Game.Graphics.Containers
|
||||
/// </summary>
|
||||
protected bool IsBeatSyncedWithTrack { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The most valid timing point, updated every frame.
|
||||
/// </summary>
|
||||
protected TimingControlPoint TimingPoint { get; private set; } = TimingControlPoint.DEFAULT;
|
||||
|
||||
/// <summary>
|
||||
/// The most valid effect point, updated every frame.
|
||||
/// </summary>
|
||||
protected EffectControlPoint EffectPoint { get; private set; } = EffectControlPoint.DEFAULT;
|
||||
|
||||
[Resolved]
|
||||
protected IBeatSyncProvider BeatSyncSource { get; private set; } = null!;
|
||||
|
||||
@ -82,9 +92,6 @@ namespace osu.Game.Graphics.Containers
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
TimingControlPoint timingPoint;
|
||||
EffectControlPoint effectPoint;
|
||||
|
||||
IsBeatSyncedWithTrack = BeatSyncSource.Clock.IsRunning;
|
||||
|
||||
double currentTrackTime;
|
||||
@ -102,8 +109,8 @@ namespace osu.Game.Graphics.Containers
|
||||
|
||||
currentTrackTime = BeatSyncSource.Clock.CurrentTime + early;
|
||||
|
||||
timingPoint = BeatSyncSource.ControlPoints?.TimingPointAt(currentTrackTime) ?? TimingControlPoint.DEFAULT;
|
||||
effectPoint = BeatSyncSource.ControlPoints?.EffectPointAt(currentTrackTime) ?? EffectControlPoint.DEFAULT;
|
||||
TimingPoint = BeatSyncSource.ControlPoints?.TimingPointAt(currentTrackTime) ?? TimingControlPoint.DEFAULT;
|
||||
EffectPoint = BeatSyncSource.ControlPoints?.EffectPointAt(currentTrackTime) ?? EffectControlPoint.DEFAULT;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -111,28 +118,28 @@ namespace osu.Game.Graphics.Containers
|
||||
// we still want to show an idle animation, so use this container's time instead.
|
||||
currentTrackTime = Clock.CurrentTime + EarlyActivationMilliseconds;
|
||||
|
||||
timingPoint = TimingControlPoint.DEFAULT;
|
||||
effectPoint = EffectControlPoint.DEFAULT;
|
||||
TimingPoint = TimingControlPoint.DEFAULT;
|
||||
EffectPoint = EffectControlPoint.DEFAULT;
|
||||
}
|
||||
|
||||
double beatLength = timingPoint.BeatLength / Divisor;
|
||||
double beatLength = TimingPoint.BeatLength / Divisor;
|
||||
|
||||
while (beatLength < MinimumBeatLength)
|
||||
beatLength *= 2;
|
||||
|
||||
int beatIndex = (int)((currentTrackTime - timingPoint.Time) / beatLength) - (timingPoint.OmitFirstBarLine ? 1 : 0);
|
||||
int beatIndex = (int)((currentTrackTime - TimingPoint.Time) / beatLength) - (TimingPoint.OmitFirstBarLine ? 1 : 0);
|
||||
|
||||
// The beats before the start of the first control point are off by 1, this should do the trick
|
||||
if (currentTrackTime < timingPoint.Time)
|
||||
if (currentTrackTime < TimingPoint.Time)
|
||||
beatIndex--;
|
||||
|
||||
TimeUntilNextBeat = (timingPoint.Time - currentTrackTime) % beatLength;
|
||||
TimeUntilNextBeat = (TimingPoint.Time - currentTrackTime) % beatLength;
|
||||
if (TimeUntilNextBeat <= 0)
|
||||
TimeUntilNextBeat += beatLength;
|
||||
|
||||
TimeSinceLastBeat = beatLength - TimeUntilNextBeat;
|
||||
|
||||
if (ReferenceEquals(timingPoint, lastTimingPoint) && beatIndex == lastBeat)
|
||||
if (ReferenceEquals(TimingPoint, lastTimingPoint) && beatIndex == lastBeat)
|
||||
return;
|
||||
|
||||
// as this event is sometimes used for sound triggers where `BeginDelayedSequence` has no effect, avoid firing it if too far away from the beat.
|
||||
@ -140,13 +147,13 @@ namespace osu.Game.Graphics.Containers
|
||||
if (AllowMistimedEventFiring || Math.Abs(TimeSinceLastBeat) < MISTIMED_ALLOWANCE)
|
||||
{
|
||||
using (BeginDelayedSequence(-TimeSinceLastBeat))
|
||||
OnNewBeat(beatIndex, timingPoint, effectPoint, BeatSyncSource.CurrentAmplitudes);
|
||||
OnNewBeat(beatIndex, TimingPoint, EffectPoint, BeatSyncSource.CurrentAmplitudes);
|
||||
}
|
||||
|
||||
lastBeat = beatIndex;
|
||||
lastTimingPoint = timingPoint;
|
||||
lastTimingPoint = TimingPoint;
|
||||
|
||||
IsKiaiTime = effectPoint.KiaiMode;
|
||||
IsKiaiTime = EffectPoint.KiaiMode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Overlays;
|
||||
using Vector2 = osuTK.Vector2;
|
||||
@ -52,10 +53,21 @@ namespace osu.Game.Graphics.UserInterface
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The action to use to reset the value of <see cref="SliderBar{T}.Current"/> to the default.
|
||||
/// Triggered on double click.
|
||||
/// </summary>
|
||||
public Action ResetToDefault { get; internal set; }
|
||||
|
||||
public RoundedSliderBar()
|
||||
{
|
||||
Height = Nub.HEIGHT;
|
||||
RangePadding = Nub.DEFAULT_EXPANDED_SIZE / 2;
|
||||
ResetToDefault = () =>
|
||||
{
|
||||
if (!Current.Disabled)
|
||||
Current.SetDefault();
|
||||
};
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
@ -102,11 +114,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
Origin = Anchor.TopCentre,
|
||||
RelativePositionAxes = Axes.X,
|
||||
Current = { Value = true },
|
||||
OnDoubleClicked = () =>
|
||||
{
|
||||
if (!Current.Disabled)
|
||||
Current.SetDefault();
|
||||
},
|
||||
OnDoubleClicked = () => ResetToDefault.Invoke(),
|
||||
},
|
||||
},
|
||||
hoverClickSounds = new HoverClickSounds()
|
||||
|
@ -140,9 +140,9 @@ namespace osu.Game.Localisation
|
||||
public static LocalisableString LoadInBrowserAfterSubmission => new TranslatableString(getKey(@"load_in_browser_after_submission"), @"Load in browser after submission");
|
||||
|
||||
/// <summary>
|
||||
/// "Note: In order to make it possible for users of all osu! versions to enjoy your beatmap, it will be exported in a backwards-compatible format. While we have made efforts to ensure that process keeps the beatmap playable in its intended form, some data related to features that previous versions of osu! do not support may be lost."
|
||||
/// "Note: In order to make it possible for users of all osu! versions to enjoy your beatmap, it will be exported in a backwards-compatible format. While we have made efforts to ensure that this process keeps the beatmap playable in its intended form, some data related to features that previous versions of osu! do not support may be lost."
|
||||
/// </summary>
|
||||
public static LocalisableString LegacyExportDisclaimer => new TranslatableString(getKey(@"legacy_export_disclaimer"), @"Note: In order to make it possible for users of all osu! versions to enjoy your beatmap, it will be exported in a backwards-compatible format. While we have made efforts to ensure that process keeps the beatmap playable in its intended form, some data related to features that previous versions of osu! do not support may be lost.");
|
||||
public static LocalisableString LegacyExportDisclaimer => new TranslatableString(getKey(@"legacy_export_disclaimer"), @"Note: In order to make it possible for users of all osu! versions to enjoy your beatmap, it will be exported in a backwards-compatible format. While we have made efforts to ensure that this process keeps the beatmap playable in its intended form, some data related to features that previous versions of osu! do not support may be lost.");
|
||||
|
||||
/// <summary>
|
||||
/// "Empty beatmaps cannot be submitted."
|
||||
|
@ -25,9 +25,9 @@ namespace osu.Game.Localisation
|
||||
public static LocalisableString HighPrecisionMouse => new TranslatableString(getKey(@"high_precision_mouse"), @"High precision mouse");
|
||||
|
||||
/// <summary>
|
||||
/// "Attempts to bypass any operation system mouse acceleration. On windows, this is equivalent to what used to be known as "Raw Input"."
|
||||
/// "Attempts to bypass any operating system mouse acceleration. On Windows, this is equivalent to what used to be known as "Raw Input"."
|
||||
/// </summary>
|
||||
public static LocalisableString HighPrecisionMouseTooltip => new TranslatableString(getKey(@"high_precision_mouse_tooltip"), @"Attempts to bypass any operation system mouse acceleration. On windows, this is equivalent to what used to be known as ""Raw Input"".");
|
||||
public static LocalisableString HighPrecisionMouseTooltip => new TranslatableString(getKey(@"high_precision_mouse_tooltip"), @"Attempts to bypass any operating system mouse acceleration. On Windows, this is equivalent to what used to be known as ""Raw Input"".");
|
||||
|
||||
/// <summary>
|
||||
/// "Confine mouse cursor to window"
|
||||
|
@ -190,7 +190,10 @@ namespace osu.Game.Online.API
|
||||
attemptConnect();
|
||||
|
||||
if (state.Value != APIState.Online)
|
||||
{
|
||||
Thread.Sleep(50);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// hard bail if we can't get a valid access token.
|
||||
@ -238,12 +241,13 @@ namespace osu.Game.Online.API
|
||||
/// <returns>Whether the connection attempt was successful.</returns>
|
||||
private void attemptConnect()
|
||||
{
|
||||
Scheduler.Add(setPlaceholderLocalUser, false);
|
||||
if (localUser.IsDefault)
|
||||
Scheduler.Add(setPlaceholderLocalUser, false);
|
||||
|
||||
// save the username at this point, if the user requested for it to be.
|
||||
config.SetValue(OsuSetting.Username, config.Get<bool>(OsuSetting.SaveUsername) ? ProvidedUsername : string.Empty);
|
||||
|
||||
if (!authentication.HasValidAccessToken)
|
||||
if (!authentication.HasValidAccessToken && HasLogin)
|
||||
{
|
||||
state.Value = APIState.Connecting;
|
||||
LastLoginError = null;
|
||||
@ -252,6 +256,10 @@ namespace osu.Game.Online.API
|
||||
{
|
||||
authentication.AuthenticateWithLogin(ProvidedUsername, password);
|
||||
}
|
||||
catch (WebRequestFlushedException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
//todo: this fails even on network-related issues. we should probably handle those differently.
|
||||
@ -312,7 +320,7 @@ namespace osu.Game.Online.API
|
||||
log.Add(@"Login no longer valid");
|
||||
Logout();
|
||||
}
|
||||
else
|
||||
else if (ex is not WebRequestFlushedException)
|
||||
{
|
||||
state.Value = APIState.Failing;
|
||||
}
|
||||
@ -493,6 +501,11 @@ namespace osu.Game.Online.API
|
||||
handleWebException(we);
|
||||
return false;
|
||||
}
|
||||
catch (WebRequestFlushedException wrf)
|
||||
{
|
||||
log.Add(wrf.Message);
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, "Error occurred while handling an API request.");
|
||||
@ -574,7 +587,7 @@ namespace osu.Game.Online.API
|
||||
if (failOldRequests)
|
||||
{
|
||||
foreach (var req in oldQueueRequests)
|
||||
req.Fail(new WebException($@"Request failed from flush operation (state {state.Value})"));
|
||||
req.Fail(new WebRequestFlushedException(state.Value));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -605,7 +618,11 @@ namespace osu.Game.Online.API
|
||||
return;
|
||||
|
||||
var friendsReq = new GetFriendsRequest();
|
||||
friendsReq.Failure += _ => state.Value = APIState.Failing;
|
||||
friendsReq.Failure += ex =>
|
||||
{
|
||||
if (ex is not WebRequestFlushedException)
|
||||
state.Value = APIState.Failing;
|
||||
};
|
||||
friendsReq.Success += res =>
|
||||
{
|
||||
var existingFriends = friends.Select(f => f.TargetID).ToHashSet();
|
||||
@ -630,6 +647,14 @@ namespace osu.Game.Online.API
|
||||
flushQueue();
|
||||
cancellationToken.Cancel();
|
||||
}
|
||||
|
||||
private class WebRequestFlushedException : Exception
|
||||
{
|
||||
public WebRequestFlushedException(APIState state)
|
||||
: base($@"Request failed from flush operation (state {state})")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class GuestUser : APIUser
|
||||
|
@ -11,6 +11,7 @@ using osu.Framework.IO.Network;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Screens.Edit.Submission;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
@ -42,22 +43,27 @@ namespace osu.Game.Online.API.Requests
|
||||
[JsonProperty("target")]
|
||||
public BeatmapSubmissionTarget SubmissionTarget { get; init; }
|
||||
|
||||
[JsonProperty("notify_on_discussion_replies")]
|
||||
public bool NotifyOnDiscussionReplies { get; init; }
|
||||
|
||||
private PutBeatmapSetRequest()
|
||||
{
|
||||
}
|
||||
|
||||
public static PutBeatmapSetRequest CreateNew(uint beatmapCount, BeatmapSubmissionTarget target) => new PutBeatmapSetRequest
|
||||
public static PutBeatmapSetRequest CreateNew(uint beatmapCount, BeatmapSubmissionSettings settings) => new PutBeatmapSetRequest
|
||||
{
|
||||
BeatmapsToCreate = beatmapCount,
|
||||
SubmissionTarget = target,
|
||||
SubmissionTarget = settings.Target.Value,
|
||||
NotifyOnDiscussionReplies = settings.NotifyOnDiscussionReplies.Value,
|
||||
};
|
||||
|
||||
public static PutBeatmapSetRequest UpdateExisting(uint beatmapSetId, IEnumerable<uint> beatmapsToKeep, uint beatmapsToCreate, BeatmapSubmissionTarget target) => new PutBeatmapSetRequest
|
||||
public static PutBeatmapSetRequest UpdateExisting(uint beatmapSetId, IEnumerable<uint> beatmapsToKeep, uint beatmapsToCreate, BeatmapSubmissionSettings settings) => new PutBeatmapSetRequest
|
||||
{
|
||||
BeatmapSetID = beatmapSetId,
|
||||
BeatmapsToKeep = beatmapsToKeep.ToArray(),
|
||||
BeatmapsToCreate = beatmapsToCreate,
|
||||
SubmissionTarget = target,
|
||||
SubmissionTarget = settings.Target.Value,
|
||||
NotifyOnDiscussionReplies = settings.NotifyOnDiscussionReplies.Value,
|
||||
};
|
||||
|
||||
protected override WebRequest CreateWebRequest()
|
||||
|
@ -14,5 +14,6 @@ namespace osu.Game.Online.Chat
|
||||
Group,
|
||||
System,
|
||||
Announce,
|
||||
Team,
|
||||
}
|
||||
}
|
||||
|
@ -271,6 +271,7 @@ namespace osu.Game.Online.Leaderboards
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Spacing = new Vector2(-10, 0),
|
||||
Direction = FillDirection.Horizontal,
|
||||
ChildrenEnumerable = Score.Mods.AsOrdered().Select(mod => new ModIcon(mod) { Scale = new Vector2(0.34f) })
|
||||
},
|
||||
@ -394,7 +395,7 @@ namespace osu.Game.Online.Leaderboards
|
||||
Origin = Anchor.CentreLeft,
|
||||
Text = statistic.Value,
|
||||
Spacing = new Vector2(-1, 0),
|
||||
Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, fixedWidth: true)
|
||||
Font = OsuFont.GetFont(size: 16, weight: FontWeight.Bold, fixedWidth: true)
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -425,7 +426,7 @@ namespace osu.Game.Online.Leaderboards
|
||||
public DateLabel(DateTimeOffset date)
|
||||
: base(date)
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 13, weight: FontWeight.Bold, italics: true);
|
||||
Font = OsuFont.GetFont(size: 16, weight: FontWeight.Bold);
|
||||
}
|
||||
|
||||
protected override string Format() => Date.ToShortRelativeTime(TimeSpan.FromSeconds(30));
|
||||
|
@ -51,6 +51,11 @@ namespace osu.Game.Online.Multiplayer
|
||||
/// </summary>
|
||||
public event Action<MultiplayerRoomUser>? UserKicked;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the room's host is changed.
|
||||
/// </summary>
|
||||
public event Action<MultiplayerRoomUser?>? HostChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a new item is added to the playlist.
|
||||
/// </summary>
|
||||
@ -531,6 +536,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
Room.Host = user;
|
||||
APIRoom.Host = user?.User;
|
||||
|
||||
HostChanged?.Invoke(user);
|
||||
RoomUpdated?.Invoke();
|
||||
}, false);
|
||||
|
||||
|
@ -13,6 +13,7 @@ namespace osu.Game.Online
|
||||
SpectatorUrl = "https://spectator.ppy.sh/spectator";
|
||||
MultiplayerUrl = "https://spectator.ppy.sh/multiplayer";
|
||||
MetadataUrl = "https://spectator.ppy.sh/metadata";
|
||||
BeatmapSubmissionServiceUrl = "https://bss.ppy.sh";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -818,7 +818,7 @@ namespace osu.Game
|
||||
/// Adjust the globally applied <see cref="DrawSizePreservingFillContainer.TargetDrawSize"/> in every <see cref="ScalingContainer"/>.
|
||||
/// Useful for changing how the game handles different aspect ratios.
|
||||
/// </summary>
|
||||
protected internal virtual Vector2 ScalingContainerTargetDrawSize { get; } = new Vector2(1024, 768);
|
||||
public virtual Vector2 ScalingContainerTargetDrawSize { get; } = new Vector2(1024, 768);
|
||||
|
||||
protected override Container CreateScalingContainer() => new ScalingContainer(ScalingMode.Everything);
|
||||
|
||||
|
@ -9,6 +9,7 @@ using osu.Framework.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Testing;
|
||||
@ -39,6 +40,7 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
||||
|
||||
public ChannelGroup AnnounceChannelGroup { get; private set; } = null!;
|
||||
public ChannelGroup PublicChannelGroup { get; private set; } = null!;
|
||||
public ChannelGroup TeamChannelGroup { get; private set; } = null!;
|
||||
public ChannelGroup PrivateChannelGroup { get; private set; } = null!;
|
||||
|
||||
private OsuScrollContainer scroll = null!;
|
||||
@ -79,10 +81,12 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
||||
RelativeSizeAxes = Axes.X,
|
||||
}
|
||||
},
|
||||
AnnounceChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitleANNOUNCE.ToUpper(), false),
|
||||
PublicChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePUBLIC.ToUpper(), false),
|
||||
// cross-reference for icons: https://github.com/ppy/osu-web/blob/3c9e99eaf4bd9e73d2712f60d67f5bc95f9dfe2b/resources/js/chat/conversation-list.tsx#L13-L19
|
||||
AnnounceChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitleANNOUNCE.ToUpper(), FontAwesome.Solid.Bullhorn, false),
|
||||
PublicChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePUBLIC.ToUpper(), FontAwesome.Solid.Comments, false),
|
||||
selector = new ChannelListItem(ChannelListingChannel),
|
||||
PrivateChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePM.ToUpper(), true),
|
||||
TeamChannelGroup = new ChannelGroup("TEAM", FontAwesome.Solid.Users, false), // TODO: replace with osu-web localisable string once available
|
||||
PrivateChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePM.ToUpper(), FontAwesome.Solid.Envelope, true),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -102,6 +106,7 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
||||
};
|
||||
|
||||
selector.OnRequestSelect += chan => OnRequestSelect?.Invoke(chan);
|
||||
updateVisibility();
|
||||
}
|
||||
|
||||
public void AddChannel(Channel channel)
|
||||
@ -109,9 +114,13 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
||||
if (channelMap.ContainsKey(channel))
|
||||
return;
|
||||
|
||||
ChannelListItem item = new ChannelListItem(channel);
|
||||
ChannelListItem item = new ChannelListItem(channel)
|
||||
{
|
||||
CanLeave = channel.Type != ChannelType.Team
|
||||
};
|
||||
item.OnRequestSelect += chan => OnRequestSelect?.Invoke(chan);
|
||||
item.OnRequestLeave += chan => OnRequestLeave?.Invoke(chan);
|
||||
if (item.CanLeave)
|
||||
item.OnRequestLeave += chan => OnRequestLeave?.Invoke(chan);
|
||||
|
||||
ChannelGroup group = getGroupFromChannel(channel);
|
||||
channelMap.Add(channel, item);
|
||||
@ -156,6 +165,9 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
||||
case ChannelType.Announce:
|
||||
return AnnounceChannelGroup;
|
||||
|
||||
case ChannelType.Team:
|
||||
return TeamChannelGroup;
|
||||
|
||||
default:
|
||||
return PublicChannelGroup;
|
||||
}
|
||||
@ -163,10 +175,8 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
||||
|
||||
private void updateVisibility()
|
||||
{
|
||||
if (AnnounceChannelGroup.ItemFlow.Children.Count == 0)
|
||||
AnnounceChannelGroup.Hide();
|
||||
else
|
||||
AnnounceChannelGroup.Show();
|
||||
AnnounceChannelGroup.Alpha = AnnounceChannelGroup.ItemFlow.Any() ? 1 : 0;
|
||||
TeamChannelGroup.Alpha = TeamChannelGroup.ItemFlow.Any() ? 1 : 0;
|
||||
}
|
||||
|
||||
public partial class ChannelGroup : FillFlowContainer
|
||||
@ -174,7 +184,7 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
||||
private readonly bool sortByRecent;
|
||||
public readonly ChannelListItemFlow ItemFlow;
|
||||
|
||||
public ChannelGroup(LocalisableString label, bool sortByRecent)
|
||||
public ChannelGroup(LocalisableString label, IconUsage icon, bool sortByRecent)
|
||||
{
|
||||
this.sortByRecent = sortByRecent;
|
||||
Direction = FillDirection.Vertical;
|
||||
@ -184,11 +194,26 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
new FillFlowContainer
|
||||
{
|
||||
Text = label,
|
||||
Margin = new MarginPadding { Left = 18, Bottom = 5 },
|
||||
Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold),
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(5),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = label,
|
||||
Margin = new MarginPadding { Left = 18, Bottom = 5 },
|
||||
Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold),
|
||||
},
|
||||
new SpriteIcon
|
||||
{
|
||||
Icon = icon,
|
||||
Size = new Vector2(12),
|
||||
},
|
||||
}
|
||||
},
|
||||
ItemFlow = new ChannelListItemFlow(sortByRecent)
|
||||
{
|
||||
|
@ -24,6 +24,8 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
||||
public partial class ChannelListItem : OsuClickableContainer, IFilterable
|
||||
{
|
||||
public event Action<Channel>? OnRequestSelect;
|
||||
|
||||
public bool CanLeave { get; init; } = true;
|
||||
public event Action<Channel>? OnRequestLeave;
|
||||
|
||||
public readonly Channel Channel;
|
||||
@ -160,7 +162,7 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
||||
|
||||
private ChannelListItemCloseButton? createCloseButton()
|
||||
{
|
||||
if (isSelector)
|
||||
if (isSelector || !CanLeave)
|
||||
return null;
|
||||
|
||||
return new ChannelListItemCloseButton
|
||||
|
@ -6,6 +6,7 @@ using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Events;
|
||||
@ -16,7 +17,6 @@ using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using SharpCompress;
|
||||
|
||||
namespace osu.Game.Overlays.Profile.Header.Components
|
||||
{
|
||||
|
@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
private EditorClock editorClock { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private EditorBeatmap editorBeatmap { get; set; } = null!;
|
||||
protected EditorBeatmap EditorBeatmap { get; private set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IBeatSnapProvider beatSnapProvider { get; set; } = null!;
|
||||
@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
}
|
||||
});
|
||||
|
||||
DistanceSpacingMultiplier.Value = editorBeatmap.DistanceSpacing;
|
||||
DistanceSpacingMultiplier.Value = EditorBeatmap.DistanceSpacing;
|
||||
DistanceSpacingMultiplier.BindValueChanged(multiplier =>
|
||||
{
|
||||
distanceSpacingSlider.ContractedLabelText = $"D. S. ({multiplier.NewValue:0.##x})";
|
||||
@ -109,7 +109,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
if (multiplier.NewValue != multiplier.OldValue)
|
||||
onScreenDisplay?.Display(new DistanceSpacingToast(multiplier.NewValue.ToLocalisableString(@"0.##x"), multiplier));
|
||||
|
||||
editorBeatmap.DistanceSpacing = multiplier.NewValue;
|
||||
EditorBeatmap.DistanceSpacing = multiplier.NewValue;
|
||||
}, true);
|
||||
|
||||
DistanceSpacingMultiplier.BindDisabledChanged(disabled => distanceSpacingSlider.Alpha = disabled ? 0 : 1, true);
|
||||
@ -267,7 +267,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
|
||||
public virtual float GetBeatSnapDistance(IHasSliderVelocity? withVelocity = null)
|
||||
{
|
||||
return (float)(100 * (withVelocity?.SliderVelocityMultiplier ?? 1) * editorBeatmap.Difficulty.SliderMultiplier * 1
|
||||
return (float)(100 * (withVelocity?.SliderVelocityMultiplier ?? 1) * EditorBeatmap.Difficulty.SliderMultiplier * 1
|
||||
/ beatSnapProvider.BeatDivisor);
|
||||
}
|
||||
|
||||
|
@ -31,12 +31,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
|
||||
protected sealed override Drawable CreateControl() => new SliderControl(sliderDisplayCurrent, CreateSlider);
|
||||
|
||||
protected virtual RoundedSliderBar<float> CreateSlider(BindableNumber<float> current) => new RoundedSliderBar<float>
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Current = current,
|
||||
KeyboardStep = 0.1f,
|
||||
};
|
||||
protected virtual RoundedSliderBar<float> CreateSlider(BindableNumber<float> current) => new RoundedSliderBar<float>();
|
||||
|
||||
/// <summary>
|
||||
/// Guards against beatmap values displayed on slider bars being transferred to user override.
|
||||
@ -111,7 +106,21 @@ namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
createSlider(currentNumber)
|
||||
createSlider(currentNumber).With(slider =>
|
||||
{
|
||||
slider.RelativeSizeAxes = Axes.X;
|
||||
slider.Current = currentNumber;
|
||||
slider.KeyboardStep = 0.1f;
|
||||
// this looks redundant, but isn't because of the various games this component plays
|
||||
// (`Current` is nullable and represents the underlying setting value,
|
||||
// `currentNumber` is not nullable and represents what is getting displayed,
|
||||
// therefore without this, double-clicking the slider would reset `currentNumber` to its bogus default of 0).
|
||||
slider.ResetToDefault = () =>
|
||||
{
|
||||
if (!Current.Disabled)
|
||||
Current.SetDefault();
|
||||
};
|
||||
})
|
||||
};
|
||||
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
@ -247,7 +247,12 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
||||
// It is required that we set a lifetime end here to ensure that in scenarios like loading a Player instance to a seeked
|
||||
// location in a beatmap doesn't churn every hit object into a DrawableHitObject. Even in a pooled scenario, the overhead
|
||||
// of this can be quite crippling.
|
||||
entry.LifetimeEnd = entry.HitObject.GetEndTime() + timeRange.Value;
|
||||
//
|
||||
// However, additionally do not attempt to alter lifetime of judged entries.
|
||||
// This is to prevent freak accidents like objects suddenly becoming alive because of this estimate assigning a later lifetime
|
||||
// than the object itself decided it should have when it underwent judgement.
|
||||
if (!entry.Judged)
|
||||
entry.LifetimeEnd = entry.HitObject.GetEndTime() + timeRange.Value;
|
||||
}
|
||||
|
||||
private void updateLayoutRecursive(DrawableHitObject hitObject, double? parentHitObjectStartTime = null)
|
||||
|
@ -398,6 +398,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
|
||||
private readonly BindableBeatDivisor beatDivisor;
|
||||
|
||||
public override bool AcceptsFocus => false;
|
||||
|
||||
public TickSliderBar(BindableBeatDivisor beatDivisor)
|
||||
{
|
||||
CurrentNumber.BindTo(this.beatDivisor = beatDivisor);
|
||||
|
@ -63,7 +63,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
const float thickness = 4;
|
||||
float diameter = (offset + (i + 1) * DistanceBetweenTicks + thickness / 2) * 2;
|
||||
|
||||
AddInternal(new Ring(StartTime, GetColourForIndexFromPlacement(i))
|
||||
AddInternal(new Ring(StartTime, GetColourForIndexFromPlacement(i), SliderVelocitySource)
|
||||
{
|
||||
Position = StartPosition,
|
||||
Origin = Anchor.Centre,
|
||||
@ -128,12 +128,14 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
private EditorClock? editorClock { get; set; }
|
||||
|
||||
private readonly double startTime;
|
||||
private readonly IHasSliderVelocity? sliderVelocitySource;
|
||||
|
||||
private readonly Color4 baseColour;
|
||||
|
||||
public Ring(double startTime, Color4 baseColour)
|
||||
public Ring(double startTime, Color4 baseColour, IHasSliderVelocity? sliderVelocitySource)
|
||||
{
|
||||
this.startTime = startTime;
|
||||
this.sliderVelocitySource = sliderVelocitySource;
|
||||
|
||||
Colour = this.baseColour = baseColour;
|
||||
|
||||
@ -150,7 +152,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
float distanceSpacingMultiplier = (float)snapProvider.DistanceSpacingMultiplier.Value;
|
||||
double timeFromReferencePoint = editorClock.CurrentTime - startTime;
|
||||
|
||||
float distanceForCurrentTime = snapProvider.DurationToDistance(timeFromReferencePoint, startTime)
|
||||
float distanceForCurrentTime = snapProvider.DurationToDistance(timeFromReferencePoint, startTime, sliderVelocitySource)
|
||||
* distanceSpacingMultiplier;
|
||||
|
||||
float timeBasedAlpha = 1 - Math.Clamp(Math.Abs(distanceForCurrentTime - Size.X / 2) / 30, 0, 1);
|
||||
|
@ -27,7 +27,6 @@ using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Edit.Components.TernaryButtons;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Compose.Components
|
||||
{
|
||||
@ -112,71 +111,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
blueprint.DrawableObject = drawableObject;
|
||||
}
|
||||
|
||||
private bool nudgeMovementActive;
|
||||
|
||||
protected override bool OnKeyDown(KeyDownEvent e)
|
||||
{
|
||||
// Until the keys below are global actions, this will prevent conflicts with "seek between sample points"
|
||||
// which has a default of ctrl+shift+arrows.
|
||||
if (e.ShiftPressed)
|
||||
return false;
|
||||
|
||||
if (e.ControlPressed)
|
||||
{
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.Left:
|
||||
return nudgeSelection(new Vector2(-1, 0));
|
||||
|
||||
case Key.Right:
|
||||
return nudgeSelection(new Vector2(1, 0));
|
||||
|
||||
case Key.Up:
|
||||
return nudgeSelection(new Vector2(0, -1));
|
||||
|
||||
case Key.Down:
|
||||
return nudgeSelection(new Vector2(0, 1));
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void OnKeyUp(KeyUpEvent e)
|
||||
{
|
||||
base.OnKeyUp(e);
|
||||
|
||||
if (nudgeMovementActive && !e.ControlPressed)
|
||||
{
|
||||
Beatmap.EndChange();
|
||||
nudgeMovementActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move the current selection spatially by the specified delta, in gamefield coordinates (ie. the same coordinates as the blueprints).
|
||||
/// </summary>
|
||||
/// <param name="delta"></param>
|
||||
private bool nudgeSelection(Vector2 delta)
|
||||
{
|
||||
if (!nudgeMovementActive)
|
||||
{
|
||||
nudgeMovementActive = true;
|
||||
Beatmap.BeginChange();
|
||||
}
|
||||
|
||||
var firstBlueprint = SelectionHandler.SelectedBlueprints.FirstOrDefault();
|
||||
|
||||
if (firstBlueprint == null)
|
||||
return false;
|
||||
|
||||
// convert to game space coordinates
|
||||
delta = firstBlueprint.ToScreenSpace(delta) - firstBlueprint.ToScreenSpace(Vector2.Zero);
|
||||
|
||||
SelectionHandler.HandleMovement(new MoveSelectionEvent<HitObject>(firstBlueprint, delta));
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updatePlacementNewCombo()
|
||||
{
|
||||
if (CurrentHitObjectPlacement?.HitObject is IHasComboInformation c)
|
||||
|
@ -11,6 +11,7 @@ using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Layout;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
@ -148,7 +149,18 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
{
|
||||
var timingPoint = Beatmap.ControlPointInfo.TimingPointAt(StartTime);
|
||||
double beatLength = timingPoint.BeatLength / beatDivisor.Value;
|
||||
int beatIndex = (int)Math.Floor((StartTime - timingPoint.Time) / beatLength);
|
||||
double fractionalBeatIndex = (StartTime - timingPoint.Time) / beatLength;
|
||||
int beatIndex = (int)Math.Round(fractionalBeatIndex);
|
||||
// `fractionalBeatIndex` could differ from `beatIndex` for two reasons:
|
||||
// - rounding errors (which can be exacerbated by timing point start times being truncated by/for stable),
|
||||
// - `StartTime` is not snapped to the beat.
|
||||
// in case 1, we want rounding to occur to prevent an off-by-one,
|
||||
// as `StartTime` *is* quantised to the beat. but it just doesn't look like it because floats do float things.
|
||||
// in case 2, we want *flooring* to occur, to prevent a possible off-by-one
|
||||
// because of the rounding snapping forward by a chunk of time significantly too high to be considered a rounding error.
|
||||
// the tolerance margin chosen here is arbitrary and can be adjusted if more cases of this are found.
|
||||
if (Precision.DefinitelyBigger(beatIndex, fractionalBeatIndex, 0.01))
|
||||
beatIndex = (int)Math.Floor(fractionalBeatIndex);
|
||||
|
||||
var colour = BindableBeatDivisor.GetColourFor(BindableBeatDivisor.GetDivisorForBeatIndex(beatIndex + placementIndex + 1, beatDivisor.Value), Colours);
|
||||
|
||||
|
@ -81,13 +81,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
double offset = result.Time.Value - referenceTime;
|
||||
|
||||
if (offset != 0)
|
||||
{
|
||||
Beatmap.PerformOnSelection(obj =>
|
||||
{
|
||||
obj.StartTime += offset;
|
||||
Beatmap.Update(obj);
|
||||
});
|
||||
}
|
||||
Beatmap.PerformOnSelection(obj => obj.StartTime += offset);
|
||||
}
|
||||
|
||||
protected override void AddBlueprintFor(HitObject item)
|
||||
|
@ -355,8 +355,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
for (int i = 0; i < hasRepeats.NodeSamples.Count; ++i)
|
||||
hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(s => s.Name == HitSampleInfo.HIT_NORMAL ? s.With(newBank: bankName) : s).ToList();
|
||||
}
|
||||
|
||||
EditorBeatmap.Update(h);
|
||||
});
|
||||
}
|
||||
|
||||
@ -390,8 +388,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(s => s.Name != HitSampleInfo.HIT_NORMAL ? bankName == HIT_BANK_AUTO ? s.With(newBank: normalBank, newEditorAutoBank: true) : s.With(newBank: bankName, newEditorAutoBank: false) : s).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
EditorBeatmap.Update(h);
|
||||
});
|
||||
}
|
||||
|
||||
@ -439,8 +435,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
node.Add(hitSample);
|
||||
}
|
||||
}
|
||||
|
||||
EditorBeatmap.Update(h);
|
||||
});
|
||||
}
|
||||
|
||||
@ -462,8 +456,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
for (int i = 0; i < hasRepeats.NodeSamples.Count; ++i)
|
||||
hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Where(s => s.Name != sampleName).ToList();
|
||||
}
|
||||
|
||||
EditorBeatmap.Update(h);
|
||||
});
|
||||
}
|
||||
|
||||
@ -484,7 +476,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
if (comboInfo == null || comboInfo.NewCombo == state) return;
|
||||
|
||||
comboInfo.NewCombo = state;
|
||||
EditorBeatmap.Update(h);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -312,8 +312,13 @@ namespace osu.Game.Screens.Edit
|
||||
return;
|
||||
|
||||
BeginChange();
|
||||
|
||||
foreach (var h in SelectedHitObjects)
|
||||
{
|
||||
action(h);
|
||||
Update(h);
|
||||
}
|
||||
|
||||
EndChange();
|
||||
}
|
||||
|
||||
|
@ -192,8 +192,8 @@ namespace osu.Game.Screens.Edit.Submission
|
||||
(uint)Beatmap.Value.BeatmapSetInfo.OnlineID,
|
||||
Beatmap.Value.BeatmapSetInfo.Beatmaps.Where(b => b.OnlineID > 0).Select(b => (uint)b.OnlineID).ToArray(),
|
||||
(uint)Beatmap.Value.BeatmapSetInfo.Beatmaps.Count(b => b.OnlineID <= 0),
|
||||
settings.Target.Value)
|
||||
: PutBeatmapSetRequest.CreateNew((uint)Beatmap.Value.BeatmapSetInfo.Beatmaps.Count, settings.Target.Value);
|
||||
settings)
|
||||
: PutBeatmapSetRequest.CreateNew((uint)Beatmap.Value.BeatmapSetInfo.Beatmaps.Count, settings);
|
||||
|
||||
createRequest.Success += async response =>
|
||||
{
|
||||
@ -249,6 +249,7 @@ namespace osu.Game.Screens.Edit.Submission
|
||||
exportProgressNotification = null;
|
||||
Logger.Log($"Beatmap set submission failed on export: {ex}");
|
||||
allowExit();
|
||||
return;
|
||||
}
|
||||
|
||||
exportStep.SetCompleted();
|
||||
@ -284,7 +285,7 @@ namespace osu.Game.Screens.Edit.Submission
|
||||
continue;
|
||||
}
|
||||
|
||||
if (localHash != onlineHash)
|
||||
if (!localHash.Equals(onlineHash, StringComparison.OrdinalIgnoreCase))
|
||||
filesToUpdate.Add(filename);
|
||||
}
|
||||
|
||||
|
@ -9,5 +9,7 @@ namespace osu.Game.Screens.Edit.Submission
|
||||
public class BeatmapSubmissionSettings
|
||||
{
|
||||
public Bindable<BeatmapSubmissionTarget> Target { get; } = new Bindable<BeatmapSubmissionTarget>();
|
||||
|
||||
public Bindable<bool> NotifyOnDiscussionReplies { get; } = new Bindable<bool>();
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Screens.Edit.Submission
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager configManager, OsuColour colours, BeatmapSubmissionSettings settings)
|
||||
{
|
||||
configManager.BindWith(OsuSetting.EditorSubmissionNotifyOnDiscussionReplies, notifyOnDiscussionReplies);
|
||||
configManager.BindWith(OsuSetting.EditorSubmissionNotifyOnDiscussionReplies, settings.NotifyOnDiscussionReplies);
|
||||
configManager.BindWith(OsuSetting.EditorSubmissionLoadInBrowserAfterSubmission, loadInBrowserAfterSubmission);
|
||||
|
||||
Content.Add(new FillFlowContainer
|
||||
@ -47,7 +47,7 @@ namespace osu.Game.Screens.Edit.Submission
|
||||
new FormCheckBox
|
||||
{
|
||||
Caption = BeatmapSubmissionStrings.NotifyOnDiscussionReplies,
|
||||
Current = notifyOnDiscussionReplies,
|
||||
Current = settings.NotifyOnDiscussionReplies,
|
||||
},
|
||||
new FormCheckBox
|
||||
{
|
||||
|
@ -3,10 +3,8 @@
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Graphics.Containers;
|
||||
|
||||
namespace osu.Game.Screens.Menu
|
||||
@ -40,27 +38,22 @@ namespace osu.Game.Screens.Menu
|
||||
|
||||
private bool isTriggered;
|
||||
|
||||
private double? lastTrigger;
|
||||
|
||||
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes)
|
||||
protected override void Update()
|
||||
{
|
||||
base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes);
|
||||
base.Update();
|
||||
|
||||
if (effectPoint.KiaiMode && !isTriggered)
|
||||
if (EffectPoint.KiaiMode && !isTriggered)
|
||||
{
|
||||
bool isNearEffectPoint = Math.Abs(BeatSyncSource.Clock.CurrentTime - effectPoint.Time) < 500;
|
||||
bool isNearEffectPoint = Math.Abs(BeatSyncSource.Clock.CurrentTime - EffectPoint.Time) < 500;
|
||||
if (isNearEffectPoint)
|
||||
Shoot();
|
||||
}
|
||||
|
||||
isTriggered = effectPoint.KiaiMode;
|
||||
isTriggered = EffectPoint.KiaiMode;
|
||||
}
|
||||
|
||||
public void Shoot()
|
||||
{
|
||||
if (lastTrigger != null && Clock.CurrentTime - lastTrigger < 500)
|
||||
return;
|
||||
|
||||
int direction = RNG.Next(-1, 2);
|
||||
|
||||
switch (direction)
|
||||
@ -80,8 +73,6 @@ namespace osu.Game.Screens.Menu
|
||||
rightFountain.Shoot(1);
|
||||
break;
|
||||
}
|
||||
|
||||
lastTrigger = Clock.CurrentTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
@ -20,7 +19,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
private Sample? userJoinedSample;
|
||||
private Sample? userLeftSample;
|
||||
private Sample? userKickedSample;
|
||||
private MultiplayerRoomUser? host;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
@ -35,25 +33,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
client.RoomUpdated += onRoomUpdated;
|
||||
client.UserJoined += onUserJoined;
|
||||
client.UserLeft += onUserLeft;
|
||||
client.UserKicked += onUserKicked;
|
||||
updateState();
|
||||
}
|
||||
|
||||
private void onRoomUpdated() => Scheduler.AddOnce(updateState);
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
if (EqualityComparer<MultiplayerRoomUser>.Default.Equals(host, client.Room?.Host))
|
||||
return;
|
||||
|
||||
// only play sound when the host changes from an already-existing host.
|
||||
if (host != null)
|
||||
Scheduler.AddOnce(() => hostChangedSample?.Play());
|
||||
|
||||
host = client.Room?.Host;
|
||||
client.HostChanged += onHostChanged;
|
||||
}
|
||||
|
||||
private void onUserJoined(MultiplayerRoomUser user)
|
||||
@ -65,16 +48,22 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
private void onUserKicked(MultiplayerRoomUser user)
|
||||
=> Scheduler.AddOnce(() => userKickedSample?.Play());
|
||||
|
||||
private void onHostChanged(MultiplayerRoomUser? host)
|
||||
{
|
||||
if (host != null)
|
||||
Scheduler.AddOnce(() => hostChangedSample?.Play());
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (client.IsNotNull())
|
||||
{
|
||||
client.RoomUpdated -= onRoomUpdated;
|
||||
client.UserJoined -= onUserJoined;
|
||||
client.UserLeft -= onUserLeft;
|
||||
client.UserKicked -= onUserKicked;
|
||||
client.HostChanged -= onHostChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,9 +5,11 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets;
|
||||
@ -53,13 +55,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
public Score? Score { get; private set; }
|
||||
|
||||
[Resolved]
|
||||
private IBindable<WorkingBeatmap> beatmap { get; set; } = null!;
|
||||
private BeatmapManager beatmapManager { get; set; } = null!;
|
||||
|
||||
private readonly AudioAdjustments clockAdjustmentsFromMods = new AudioAdjustments();
|
||||
private readonly BindableDouble volumeAdjustment = new BindableDouble();
|
||||
private readonly Container gameplayContent;
|
||||
private readonly LoadingLayer loadingLayer;
|
||||
private OsuScreenStack? stack;
|
||||
private Track? loadedTrack;
|
||||
|
||||
public PlayerArea(int userId, SpectatorPlayerClock clock)
|
||||
{
|
||||
@ -89,7 +92,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
|
||||
Score = score;
|
||||
|
||||
gameplayContent.Child = new PlayerIsolationContainer(beatmap.Value, Score.ScoreInfo.Ruleset, Score.ScoreInfo.Mods)
|
||||
// Required for freestyle, where each player may be playing a different beatmap.
|
||||
var workingBeatmap = beatmapManager.GetWorkingBeatmap(Score.ScoreInfo.BeatmapInfo);
|
||||
|
||||
// Required to avoid crashes, but we really don't want to be doing this if we can avoid it.
|
||||
// If we get to fixing this, we will want to investigate every access to `Track` in gameplay.
|
||||
if (!workingBeatmap.TrackLoaded)
|
||||
loadedTrack = workingBeatmap.LoadTrack();
|
||||
|
||||
gameplayContent.Child = new PlayerIsolationContainer(workingBeatmap, Score.ScoreInfo.Ruleset, Score.ScoreInfo.Mods)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = stack = new OsuScreenStack
|
||||
@ -118,8 +129,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
get => mute;
|
||||
set
|
||||
{
|
||||
if (mute == value)
|
||||
return;
|
||||
|
||||
mute = value;
|
||||
volumeAdjustment.Value = value ? 0 : 1;
|
||||
Logger.Log($"{(mute ? "muting" : "unmuting")} player {UserId}");
|
||||
}
|
||||
}
|
||||
|
||||
@ -127,18 +142,27 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
public override bool PropagatePositionalInputSubTree => false;
|
||||
public override bool PropagateNonPositionalInputSubTree => false;
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
loadedTrack?.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Isolates each player instance from the game-wide ruleset/beatmap/mods (to allow for different players having different settings).
|
||||
/// </summary>
|
||||
private partial class PlayerIsolationContainer : Container
|
||||
{
|
||||
[Cached]
|
||||
[Cached(typeof(IBindable<RulesetInfo>))]
|
||||
private readonly Bindable<RulesetInfo> ruleset = new Bindable<RulesetInfo>();
|
||||
|
||||
[Cached]
|
||||
[Cached(typeof(IBindable<WorkingBeatmap>))]
|
||||
private readonly Bindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
|
||||
|
||||
[Cached]
|
||||
[Cached(typeof(IBindable<IReadOnlyList<Mod>>))]
|
||||
private readonly Bindable<IReadOnlyList<Mod>> mods = new Bindable<IReadOnlyList<Mod>>();
|
||||
|
||||
public PlayerIsolationContainer(WorkingBeatmap beatmap, RulesetInfo ruleset, IReadOnlyList<Mod> mods)
|
||||
|
@ -3,8 +3,10 @@
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Skinning;
|
||||
@ -40,10 +42,19 @@ namespace osu.Game.Screens.Play.HUD
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
private void load(UserLookupCache userLookupCache)
|
||||
{
|
||||
if (gameplayState != null)
|
||||
flag.Team = gameplayState.Score.ScoreInfo.User.Team;
|
||||
{
|
||||
if (gameplayState.Score.ScoreInfo.User.Team != null)
|
||||
flag.Team = gameplayState.Score.ScoreInfo.User.Team;
|
||||
else
|
||||
{
|
||||
// We only store very basic information about a user to realm, so there's a high chance we don't have the team information.
|
||||
userLookupCache.GetUserAsync(gameplayState.Score.ScoreInfo.User.Id)
|
||||
.ContinueWith(task => Schedule(() => flag.Team = task.GetResultSafely()?.Team));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
apiUser = api.LocalUser.GetBoundCopy();
|
||||
|
@ -414,7 +414,7 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
case GlobalAction.HoldForHUD:
|
||||
holdingForHUD.Value = true;
|
||||
return true;
|
||||
return false;
|
||||
|
||||
case GlobalAction.ToggleInGameInterface:
|
||||
switch (configVisibilityMode.Value)
|
||||
|
@ -1,15 +1,12 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Screens.Menu;
|
||||
|
||||
@ -48,33 +45,27 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
private bool isTriggered;
|
||||
|
||||
private double? lastTrigger;
|
||||
|
||||
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes)
|
||||
protected override void Update()
|
||||
{
|
||||
base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes);
|
||||
base.Update();
|
||||
|
||||
if (!kiaiStarFountains.Value)
|
||||
return;
|
||||
|
||||
if (effectPoint.KiaiMode && !isTriggered)
|
||||
if (EffectPoint.KiaiMode && !isTriggered)
|
||||
{
|
||||
bool isNearEffectPoint = Math.Abs(BeatSyncSource.Clock.CurrentTime - effectPoint.Time) < 500;
|
||||
bool isNearEffectPoint = Math.Abs(BeatSyncSource.Clock.CurrentTime - EffectPoint.Time) < 500;
|
||||
if (isNearEffectPoint)
|
||||
Shoot();
|
||||
}
|
||||
|
||||
isTriggered = effectPoint.KiaiMode;
|
||||
isTriggered = EffectPoint.KiaiMode;
|
||||
}
|
||||
|
||||
public void Shoot()
|
||||
{
|
||||
if (lastTrigger != null && Clock.CurrentTime - lastTrigger < 500)
|
||||
return;
|
||||
|
||||
leftFountain.Shoot(1);
|
||||
rightFountain.Shoot(-1);
|
||||
lastTrigger = Clock.CurrentTime;
|
||||
}
|
||||
|
||||
public partial class GameplayStarFountain : StarFountain
|
||||
|
@ -317,6 +317,8 @@ namespace osu.Game.Screens.Ranking
|
||||
if (!this.IsCurrentScreen() || s != rankApplauseSound)
|
||||
return;
|
||||
|
||||
AddInternal(rankApplauseSound);
|
||||
|
||||
rankApplauseSound.VolumeTo(applause_volume);
|
||||
rankApplauseSound.Play();
|
||||
});
|
||||
|
@ -267,9 +267,9 @@ namespace osu.Game.Screens.SelectV2
|
||||
|
||||
#region Drawable pooling
|
||||
|
||||
private readonly DrawablePool<BeatmapPanel> beatmapPanelPool = new DrawablePool<BeatmapPanel>(100);
|
||||
private readonly DrawablePool<BeatmapSetPanel> setPanelPool = new DrawablePool<BeatmapSetPanel>(100);
|
||||
private readonly DrawablePool<GroupPanel> groupPanelPool = new DrawablePool<GroupPanel>(100);
|
||||
private readonly DrawablePool<PanelBeatmap> beatmapPanelPool = new DrawablePool<PanelBeatmap>(100);
|
||||
private readonly DrawablePool<PanelBeatmapSet> setPanelPool = new DrawablePool<PanelBeatmapSet>(100);
|
||||
private readonly DrawablePool<PanelGroup> groupPanelPool = new DrawablePool<PanelGroup>(100);
|
||||
|
||||
private void setupPools()
|
||||
{
|
||||
|
@ -70,7 +70,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
|
||||
addItem(new CarouselItem(newGroup)
|
||||
{
|
||||
DrawHeight = GroupPanel.HEIGHT,
|
||||
DrawHeight = PanelGroup.HEIGHT,
|
||||
DepthLayer = -2,
|
||||
});
|
||||
}
|
||||
@ -85,7 +85,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
|
||||
addItem(new CarouselItem(beatmap.BeatmapSet!)
|
||||
{
|
||||
DrawHeight = BeatmapSetPanel.HEIGHT,
|
||||
DrawHeight = PanelBeatmapSet.HEIGHT,
|
||||
DepthLayer = -1
|
||||
});
|
||||
}
|
||||
|
@ -1,119 +0,0 @@
|
||||
// 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.Diagnostics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
public partial class BeatmapPanel : PoolableDrawable, ICarouselPanel
|
||||
{
|
||||
[Resolved]
|
||||
private BeatmapCarousel carousel { get; set; } = null!;
|
||||
|
||||
private Box activationFlash = null!;
|
||||
private OsuSpriteText text = null!;
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
|
||||
{
|
||||
var inputRectangle = DrawRectangle;
|
||||
|
||||
// Cover the gaps introduced by the spacing between BeatmapPanels so that clicks will not fall through the carousel.
|
||||
//
|
||||
// Caveat is that for simplicity, we are covering the full spacing, so panels with frontmost depth will have a slightly
|
||||
// larger hit target.
|
||||
inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING });
|
||||
|
||||
return inputRectangle.Contains(ToLocalSpace(screenSpacePos));
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Size = new Vector2(500, CarouselItem.DEFAULT_HEIGHT);
|
||||
Masking = true;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = Color4.Aqua.Darken(5),
|
||||
Alpha = 0.8f,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
activationFlash = new Box
|
||||
{
|
||||
Colour = Color4.White,
|
||||
Blending = BlendingParameters.Additive,
|
||||
Alpha = 0,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
text = new OsuSpriteText
|
||||
{
|
||||
Padding = new MarginPadding(5),
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
}
|
||||
};
|
||||
|
||||
Selected.BindValueChanged(value =>
|
||||
{
|
||||
activationFlash.FadeTo(value.NewValue ? 0.2f : 0, 500, Easing.OutQuint);
|
||||
});
|
||||
|
||||
KeyboardSelected.BindValueChanged(value =>
|
||||
{
|
||||
if (value.NewValue)
|
||||
{
|
||||
BorderThickness = 5;
|
||||
BorderColour = Color4.Pink;
|
||||
}
|
||||
else
|
||||
{
|
||||
BorderThickness = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected override void PrepareForUse()
|
||||
{
|
||||
base.PrepareForUse();
|
||||
|
||||
Debug.Assert(Item != null);
|
||||
var beatmap = (BeatmapInfo)Item.Model;
|
||||
|
||||
text.Text = $"Difficulty: {beatmap.DifficultyName} ({beatmap.StarRating:N1}*)";
|
||||
|
||||
this.FadeInFromZero(500, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
carousel.Activate(Item!);
|
||||
return true;
|
||||
}
|
||||
|
||||
#region ICarouselPanel
|
||||
|
||||
public CarouselItem? Item { get; set; }
|
||||
public BindableBool Selected { get; } = new BindableBool();
|
||||
public BindableBool Expanded { get; } = new BindableBool();
|
||||
public BindableBool KeyboardSelected { get; } = new BindableBool();
|
||||
|
||||
public double DrawYPosition { get; set; }
|
||||
|
||||
public void Activated() => activationFlash.FadeOutFromOne(500, Easing.OutQuint);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -1,104 +0,0 @@
|
||||
// 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.Diagnostics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
public partial class BeatmapSetPanel : PoolableDrawable, ICarouselPanel
|
||||
{
|
||||
public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT * 2;
|
||||
|
||||
[Resolved]
|
||||
private BeatmapCarousel carousel { get; set; } = null!;
|
||||
|
||||
private OsuSpriteText text = null!;
|
||||
private Box box = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Size = new Vector2(500, HEIGHT);
|
||||
Masking = true;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
box = new Box
|
||||
{
|
||||
Colour = Color4.Yellow.Darken(5),
|
||||
Alpha = 0.8f,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
text = new OsuSpriteText
|
||||
{
|
||||
Padding = new MarginPadding(5),
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
}
|
||||
};
|
||||
|
||||
Expanded.BindValueChanged(value =>
|
||||
{
|
||||
box.FadeColour(value.NewValue ? Color4.Yellow.Darken(2) : Color4.Yellow.Darken(5), 500, Easing.OutQuint);
|
||||
});
|
||||
|
||||
KeyboardSelected.BindValueChanged(value =>
|
||||
{
|
||||
if (value.NewValue)
|
||||
{
|
||||
BorderThickness = 5;
|
||||
BorderColour = Color4.Pink;
|
||||
}
|
||||
else
|
||||
{
|
||||
BorderThickness = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected override void PrepareForUse()
|
||||
{
|
||||
base.PrepareForUse();
|
||||
|
||||
Debug.Assert(Item != null);
|
||||
|
||||
var beatmapSetInfo = (BeatmapSetInfo)Item.Model;
|
||||
|
||||
text.Text = $"{beatmapSetInfo.Metadata}";
|
||||
|
||||
this.FadeInFromZero(500, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
carousel.Activate(Item!);
|
||||
return true;
|
||||
}
|
||||
|
||||
#region ICarouselPanel
|
||||
|
||||
public CarouselItem? Item { get; set; }
|
||||
public BindableBool Selected { get; } = new BindableBool();
|
||||
public BindableBool Expanded { get; } = new BindableBool();
|
||||
public BindableBool KeyboardSelected { get; } = new BindableBool();
|
||||
|
||||
public double DrawYPosition { get; set; }
|
||||
|
||||
public void Activated()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
108
osu.Game/Screens/SelectV2/BeatmapSetPanelBackground.cs
Normal file
108
osu.Game/Screens/SelectV2/BeatmapSetPanelBackground.cs
Normal file
@ -0,0 +1,108 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
public partial class BeatmapSetPanelBackground : ModelBackedDrawable<WorkingBeatmap>
|
||||
{
|
||||
protected override bool TransformImmediately => true;
|
||||
|
||||
public WorkingBeatmap? Beatmap
|
||||
{
|
||||
get => Model;
|
||||
set => Model = value;
|
||||
}
|
||||
|
||||
protected override Drawable CreateDrawable(WorkingBeatmap? model) => new BackgroundSprite(model);
|
||||
|
||||
private partial class BackgroundSprite : CompositeDrawable
|
||||
{
|
||||
private readonly WorkingBeatmap? working;
|
||||
|
||||
public BackgroundSprite(WorkingBeatmap? working)
|
||||
{
|
||||
this.working = working;
|
||||
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
var texture = working?.GetPanelBackground();
|
||||
|
||||
if (texture != null)
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Sprite
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
FillMode = FillMode.Fill,
|
||||
Texture = texture,
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
Depth = -1,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
// This makes the gradient not be perfectly horizontal, but diagonal at a ~40° angle
|
||||
Shear = new Vector2(0.8f, 0),
|
||||
Alpha = 0.5f,
|
||||
Children = new[]
|
||||
{
|
||||
// The left half with no gradient applied
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Black,
|
||||
Width = 0.4f,
|
||||
},
|
||||
// Piecewise-linear gradient with 3 segments to make it appear smoother
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourInfo.GradientHorizontal(Color4.Black, new Color4(0f, 0f, 0f, 0.9f)),
|
||||
Width = 0.05f,
|
||||
},
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourInfo.GradientHorizontal(new Color4(0f, 0f, 0f, 0.9f), new Color4(0f, 0f, 0f, 0.1f)),
|
||||
Width = 0.2f,
|
||||
},
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourInfo.GradientHorizontal(new Color4(0f, 0f, 0f, 0.1f), new Color4(0, 0, 0, 0)),
|
||||
Width = 0.05f,
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
InternalChild = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background6,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
/// </summary>
|
||||
public sealed class CarouselItem : IComparable<CarouselItem>
|
||||
{
|
||||
public const float DEFAULT_HEIGHT = 40;
|
||||
public const float DEFAULT_HEIGHT = 50;
|
||||
|
||||
/// <summary>
|
||||
/// The model this item is representing.
|
||||
|
@ -1,117 +0,0 @@
|
||||
// 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.Diagnostics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
public partial class GroupPanel : PoolableDrawable, ICarouselPanel
|
||||
{
|
||||
public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT * 2;
|
||||
|
||||
[Resolved]
|
||||
private BeatmapCarousel carousel { get; set; } = null!;
|
||||
|
||||
private Box activationFlash = null!;
|
||||
private OsuSpriteText text = null!;
|
||||
|
||||
private Box box = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Size = new Vector2(500, HEIGHT);
|
||||
Masking = true;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
box = new Box
|
||||
{
|
||||
Colour = Color4.DarkBlue.Darken(5),
|
||||
Alpha = 0.8f,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
activationFlash = new Box
|
||||
{
|
||||
Colour = Color4.White,
|
||||
Blending = BlendingParameters.Additive,
|
||||
Alpha = 0,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
text = new OsuSpriteText
|
||||
{
|
||||
Padding = new MarginPadding(5),
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
}
|
||||
};
|
||||
|
||||
Selected.BindValueChanged(value =>
|
||||
{
|
||||
activationFlash.FadeTo(value.NewValue ? 0.2f : 0, 500, Easing.OutQuint);
|
||||
});
|
||||
|
||||
Expanded.BindValueChanged(value =>
|
||||
{
|
||||
box.FadeColour(value.NewValue ? Color4.SkyBlue : Color4.DarkBlue.Darken(5), 500, Easing.OutQuint);
|
||||
});
|
||||
|
||||
KeyboardSelected.BindValueChanged(value =>
|
||||
{
|
||||
if (value.NewValue)
|
||||
{
|
||||
BorderThickness = 5;
|
||||
BorderColour = Color4.Pink;
|
||||
}
|
||||
else
|
||||
{
|
||||
BorderThickness = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected override void PrepareForUse()
|
||||
{
|
||||
base.PrepareForUse();
|
||||
|
||||
Debug.Assert(Item != null);
|
||||
|
||||
GroupDefinition group = (GroupDefinition)Item.Model;
|
||||
|
||||
text.Text = group.Title;
|
||||
|
||||
this.FadeInFromZero(500, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
carousel.Activate(Item!);
|
||||
return true;
|
||||
}
|
||||
|
||||
#region ICarouselPanel
|
||||
|
||||
public CarouselItem? Item { get; set; }
|
||||
public BindableBool Selected { get; } = new BindableBool();
|
||||
public BindableBool Expanded { get; } = new BindableBool();
|
||||
public BindableBool KeyboardSelected { get; } = new BindableBool();
|
||||
|
||||
public double DrawYPosition { get; set; }
|
||||
|
||||
public void Activated()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
258
osu.Game/Screens/SelectV2/PanelBase.cs
Normal file
258
osu.Game/Screens/SelectV2/PanelBase.cs
Normal file
@ -0,0 +1,258 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
public abstract partial class PanelBase : PoolableDrawable, ICarouselPanel
|
||||
{
|
||||
private const float corner_radius = 10;
|
||||
|
||||
private const float left_edge_x_offset = 20f;
|
||||
private const float keyboard_active_x_offset = 25f;
|
||||
private const float active_x_offset = 50f;
|
||||
|
||||
private const float duration = 500;
|
||||
|
||||
protected float PanelXOffset { get; init; }
|
||||
|
||||
private Box backgroundBorder = null!;
|
||||
private Box backgroundGradient = null!;
|
||||
private Box backgroundAccentGradient = null!;
|
||||
private Container backgroundLayer = null!;
|
||||
private Container backgroundLayerHorizontalPadding = null!;
|
||||
private Container backgroundContainer = null!;
|
||||
private Container iconContainer = null!;
|
||||
private Box activationFlash = null!;
|
||||
private Box hoverLayer = null!;
|
||||
|
||||
public Container TopLevelContent { get; private set; } = null!;
|
||||
|
||||
protected Container Content { get; private set; } = null!;
|
||||
|
||||
public Drawable Background { set => backgroundContainer.Child = value; }
|
||||
|
||||
public Drawable Icon { set => iconContainer.Child = value; }
|
||||
|
||||
private Color4? accentColour;
|
||||
|
||||
public Color4? AccentColour
|
||||
{
|
||||
get => accentColour;
|
||||
set
|
||||
{
|
||||
accentColour = value;
|
||||
updateDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider, OsuColour colours)
|
||||
{
|
||||
Anchor = Anchor.TopRight;
|
||||
Origin = Anchor.TopRight;
|
||||
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Height = CarouselItem.DEFAULT_HEIGHT;
|
||||
|
||||
InternalChild = TopLevelContent = new Container
|
||||
{
|
||||
Masking = true,
|
||||
CornerRadius = corner_radius,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
X = corner_radius,
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Offset = new Vector2(1f),
|
||||
Radius = 10,
|
||||
},
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new BufferedContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
backgroundBorder = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.White,
|
||||
},
|
||||
backgroundLayerHorizontalPadding = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = backgroundLayer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new Container
|
||||
{
|
||||
Masking = true,
|
||||
CornerRadius = corner_radius,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
backgroundGradient = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
backgroundAccentGradient = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
backgroundContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
iconContainer = new Container
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
},
|
||||
Content = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Right = PanelXOffset + corner_radius },
|
||||
},
|
||||
hoverLayer = new Box
|
||||
{
|
||||
Alpha = 0,
|
||||
Blending = BlendingParameters.Additive,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
activationFlash = new Box
|
||||
{
|
||||
Colour = Color4.White.Opacity(0.4f),
|
||||
Blending = BlendingParameters.Additive,
|
||||
Alpha = 0f,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new HoverSounds(),
|
||||
}
|
||||
};
|
||||
|
||||
hoverLayer.Colour = colours.Blue.Opacity(0.1f);
|
||||
backgroundGradient.Colour = ColourInfo.GradientHorizontal(colourProvider.Background3, colourProvider.Background4);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Expanded.BindValueChanged(_ => updateDisplay());
|
||||
KeyboardSelected.BindValueChanged(_ => updateDisplay(), true);
|
||||
}
|
||||
|
||||
protected override void PrepareForUse()
|
||||
{
|
||||
base.PrepareForUse();
|
||||
this.FadeInFromZero(duration, Easing.OutQuint);
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private BeatmapCarousel? carousel { get; set; }
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
activationFlash.FadeOutFromOne(500, Easing.OutQuint);
|
||||
carousel?.Activate(Item!);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateDisplay()
|
||||
{
|
||||
backgroundLayer.TransformTo(nameof(Padding), backgroundLayer.Padding with { Vertical = Expanded.Value ? 2f : 0f }, duration, Easing.OutQuint);
|
||||
|
||||
var backgroundColour = accentColour ?? Color4.White;
|
||||
var edgeEffectColour = accentColour ?? Color4Extensions.FromHex(@"4EBFFF");
|
||||
|
||||
backgroundAccentGradient.FadeColour(ColourInfo.GradientHorizontal(backgroundColour.Opacity(0.25f), backgroundColour.Opacity(0f)), duration, Easing.OutQuint);
|
||||
backgroundBorder.FadeColour(backgroundColour, duration, Easing.OutQuint);
|
||||
|
||||
TopLevelContent.FadeEdgeEffectTo(Expanded.Value ? edgeEffectColour.Opacity(0.5f) : Color4.Black.Opacity(0.4f), duration, Easing.OutQuint);
|
||||
|
||||
updateXOffset();
|
||||
updateHover();
|
||||
}
|
||||
|
||||
private void updateXOffset()
|
||||
{
|
||||
float x = PanelXOffset + active_x_offset + keyboard_active_x_offset + left_edge_x_offset;
|
||||
|
||||
if (Expanded.Value)
|
||||
x -= active_x_offset;
|
||||
|
||||
if (KeyboardSelected.Value)
|
||||
x -= keyboard_active_x_offset;
|
||||
|
||||
this.TransformTo(nameof(Padding), new MarginPadding { Left = x }, duration, Easing.OutQuint);
|
||||
}
|
||||
|
||||
private void updateHover()
|
||||
{
|
||||
bool hovered = IsHovered || KeyboardSelected.Value;
|
||||
|
||||
if (hovered)
|
||||
hoverLayer.FadeIn(100, Easing.OutQuint);
|
||||
else
|
||||
hoverLayer.FadeOut(1000, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
updateDisplay();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
updateDisplay();
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
Content.Padding = Content.Padding with { Left = iconContainer.DrawWidth };
|
||||
backgroundLayerHorizontalPadding.Padding = new MarginPadding { Left = iconContainer.DrawWidth };
|
||||
}
|
||||
|
||||
#region ICarouselPanel
|
||||
|
||||
public CarouselItem? Item { get; set; }
|
||||
public BindableBool Selected { get; } = new BindableBool();
|
||||
public BindableBool Expanded { get; } = new BindableBool();
|
||||
public BindableBool KeyboardSelected { get; } = new BindableBool();
|
||||
|
||||
public double DrawYPosition { get; set; }
|
||||
|
||||
public virtual void Activated()
|
||||
{
|
||||
activationFlash.FadeOutFromOne(500, Easing.OutQuint);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
246
osu.Game/Screens/SelectV2/PanelBeatmap.cs
Normal file
246
osu.Game/Screens/SelectV2/PanelBeatmap.cs
Normal file
@ -0,0 +1,246 @@
|
||||
// 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.Diagnostics;
|
||||
using System.Threading;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
public partial class PanelBeatmap : PanelBase
|
||||
{
|
||||
public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT;
|
||||
|
||||
private StarCounter starCounter = null!;
|
||||
private ConstrainedIconContainer difficultyIcon = null!;
|
||||
private OsuSpriteText keyCountText = null!;
|
||||
private StarRatingDisplay starRatingDisplay = null!;
|
||||
private TopLocalRank difficultyRank = null!;
|
||||
private OsuSpriteText difficultyText = null!;
|
||||
private OsuSpriteText authorText = null!;
|
||||
|
||||
private IBindable<StarDifficulty?>? starDifficultyBindable;
|
||||
private CancellationTokenSource? starDifficultyCancellationSource;
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private BeatmapDifficultyCache difficultyCache { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IBindable<RulesetInfo> ruleset { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IBindable<IReadOnlyList<Mod>> mods { get; set; } = null!;
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
|
||||
{
|
||||
var inputRectangle = DrawRectangle;
|
||||
|
||||
// Cover the gaps introduced by the spacing between BeatmapPanels so that clicks will not fall through the carousel.
|
||||
//
|
||||
// Caveat is that for simplicity, we are covering the full spacing, so panels with frontmost depth will have a slightly
|
||||
// larger hit target.
|
||||
inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING });
|
||||
|
||||
return inputRectangle.Contains(ToLocalSpace(screenSpacePos));
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
Height = HEIGHT;
|
||||
|
||||
Icon = difficultyIcon = new ConstrainedIconContainer
|
||||
{
|
||||
Size = new Vector2(20),
|
||||
Margin = new MarginPadding { Horizontal = 5f },
|
||||
Colour = colourProvider.Background5,
|
||||
};
|
||||
|
||||
Content.Children = new[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Padding = new MarginPadding { Left = 10f },
|
||||
Direction = FillDirection.Vertical,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
{
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(3, 0),
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
starRatingDisplay = new StarRatingDisplay(default, StarRatingDisplaySize.Small)
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
},
|
||||
difficultyRank = new TopLocalRank
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Scale = new Vector2(0.75f)
|
||||
},
|
||||
starCounter = new StarCounter
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Scale = new Vector2(0.4f)
|
||||
}
|
||||
}
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
Direction = FillDirection.Horizontal,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Children = new[]
|
||||
{
|
||||
keyCountText = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 18, weight: FontWeight.SemiBold),
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Alpha = 0,
|
||||
},
|
||||
difficultyText = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 18, weight: FontWeight.SemiBold),
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Margin = new MarginPadding { Right = 8f },
|
||||
},
|
||||
authorText = new OsuSpriteText
|
||||
{
|
||||
Colour = colourProvider.Content2,
|
||||
Font = OsuFont.GetFont(weight: FontWeight.SemiBold),
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
ruleset.BindValueChanged(_ =>
|
||||
{
|
||||
computeStarRating();
|
||||
updateKeyCount();
|
||||
});
|
||||
|
||||
mods.BindValueChanged(_ =>
|
||||
{
|
||||
computeStarRating();
|
||||
updateKeyCount();
|
||||
}, true);
|
||||
|
||||
Selected.BindValueChanged(s => Expanded.Value = s.NewValue, true);
|
||||
}
|
||||
|
||||
protected override void PrepareForUse()
|
||||
{
|
||||
base.PrepareForUse();
|
||||
|
||||
Debug.Assert(Item != null);
|
||||
var beatmap = (BeatmapInfo)Item.Model;
|
||||
|
||||
difficultyIcon.Icon = beatmap.Ruleset.CreateInstance().CreateIcon();
|
||||
|
||||
difficultyRank.Beatmap = beatmap;
|
||||
difficultyText.Text = beatmap.DifficultyName;
|
||||
authorText.Text = BeatmapsetsStrings.ShowDetailsMappedBy(beatmap.Metadata.Author.Username);
|
||||
|
||||
computeStarRating();
|
||||
updateKeyCount();
|
||||
}
|
||||
|
||||
protected override void FreeAfterUse()
|
||||
{
|
||||
base.FreeAfterUse();
|
||||
|
||||
difficultyRank.Beatmap = null;
|
||||
starDifficultyBindable = null;
|
||||
}
|
||||
|
||||
private void computeStarRating()
|
||||
{
|
||||
starDifficultyCancellationSource?.Cancel();
|
||||
starDifficultyCancellationSource = new CancellationTokenSource();
|
||||
|
||||
if (Item == null)
|
||||
return;
|
||||
|
||||
var beatmap = (BeatmapInfo)Item.Model;
|
||||
|
||||
starDifficultyBindable = difficultyCache.GetBindableDifficulty(beatmap, starDifficultyCancellationSource.Token);
|
||||
starDifficultyBindable.BindValueChanged(_ => updateDisplay(), true);
|
||||
}
|
||||
|
||||
private void updateKeyCount()
|
||||
{
|
||||
if (Item == null)
|
||||
return;
|
||||
|
||||
var beatmap = (BeatmapInfo)Item.Model;
|
||||
|
||||
if (ruleset.Value.OnlineID == 3)
|
||||
{
|
||||
// Account for mania differences locally for now.
|
||||
// Eventually this should be handled in a more modular way, allowing rulesets to add more information to the panel.
|
||||
ILegacyRuleset legacyRuleset = (ILegacyRuleset)ruleset.Value.CreateInstance();
|
||||
int keyCount = legacyRuleset.GetKeyCount(beatmap, mods.Value);
|
||||
|
||||
keyCountText.Alpha = 1;
|
||||
keyCountText.Text = $"[{keyCount}K] ";
|
||||
}
|
||||
else
|
||||
keyCountText.Alpha = 0;
|
||||
}
|
||||
|
||||
private void updateDisplay()
|
||||
{
|
||||
const float duration = 500;
|
||||
|
||||
var starDifficulty = starDifficultyBindable?.Value ?? default;
|
||||
|
||||
starRatingDisplay.Current.Value = starDifficulty;
|
||||
starCounter.Current = (float)starDifficulty.Stars;
|
||||
|
||||
difficultyIcon.FadeColour(starDifficulty.Stars > 6.5f ? colours.Orange1 : colourProvider.Background5, duration, Easing.OutQuint);
|
||||
|
||||
var starRatingColour = colours.ForStarDifficulty(starDifficulty.Stars);
|
||||
starCounter.FadeColour(starRatingColour, duration, Easing.OutQuint);
|
||||
AccentColour = starRatingColour;
|
||||
}
|
||||
}
|
||||
}
|
164
osu.Game/Screens/SelectV2/PanelBeatmapSet.cs
Normal file
164
osu.Game/Screens/SelectV2/PanelBeatmapSet.cs
Normal file
@ -0,0 +1,164 @@
|
||||
// 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.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
public partial class PanelBeatmapSet : PanelBase
|
||||
{
|
||||
public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT * 1.6f;
|
||||
|
||||
private BeatmapSetPanelBackground background = null!;
|
||||
|
||||
private OsuSpriteText titleText = null!;
|
||||
private OsuSpriteText artistText = null!;
|
||||
private Drawable chevronIcon = null!;
|
||||
private UpdateBeatmapSetButton updateButton = null!;
|
||||
private BeatmapSetOnlineStatusPill statusPill = null!;
|
||||
private DifficultySpectrumDisplay difficultiesDisplay = null!;
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private BeatmapManager beatmaps { get; set; } = null!;
|
||||
|
||||
public PanelBeatmapSet()
|
||||
{
|
||||
PanelXOffset = 20f;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Height = HEIGHT;
|
||||
|
||||
Icon = chevronIcon = new Container
|
||||
{
|
||||
Size = new Vector2(22),
|
||||
Child = new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Icon = FontAwesome.Solid.ChevronRight,
|
||||
Size = new Vector2(12),
|
||||
X = 1f,
|
||||
Colour = colourProvider.Background5,
|
||||
},
|
||||
};
|
||||
|
||||
Background = background = new BeatmapSetPanelBackground
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
};
|
||||
|
||||
Content.Children = new[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Padding = new MarginPadding { Top = 7.5f, Left = 15, Bottom = 5 },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
titleText = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 22, italics: true),
|
||||
},
|
||||
artistText = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 17, italics: true),
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
Direction = FillDirection.Horizontal,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Margin = new MarginPadding { Top = 5f },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
updateButton = new UpdateBeatmapSetButton
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Margin = new MarginPadding { Right = 5f, Top = -2f },
|
||||
},
|
||||
statusPill = new BeatmapSetOnlineStatusPill
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
TextSize = 11,
|
||||
TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 },
|
||||
Margin = new MarginPadding { Right = 5f },
|
||||
},
|
||||
difficultiesDisplay = new DifficultySpectrumDisplay
|
||||
{
|
||||
DotSize = new Vector2(5, 10),
|
||||
DotSpacing = 2,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Expanded.BindValueChanged(_ => onExpanded(), true);
|
||||
KeyboardSelected.BindValueChanged(k => KeyboardSelected.Value = k.NewValue, true);
|
||||
}
|
||||
|
||||
private void onExpanded()
|
||||
{
|
||||
const float duration = 500;
|
||||
|
||||
chevronIcon.ResizeWidthTo(Expanded.Value ? 22 : 0f, duration, Easing.OutQuint);
|
||||
chevronIcon.FadeTo(Expanded.Value ? 1f : 0f, duration, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override void PrepareForUse()
|
||||
{
|
||||
base.PrepareForUse();
|
||||
|
||||
Debug.Assert(Item != null);
|
||||
|
||||
var beatmapSet = (BeatmapSetInfo)Item.Model;
|
||||
|
||||
// Choice of background image matches BSS implementation (always uses the lowest `beatmap_id` from the set).
|
||||
background.Beatmap = beatmaps.GetWorkingBeatmap(beatmapSet.Beatmaps.MinBy(b => b.OnlineID));
|
||||
|
||||
titleText.Text = new RomanisableString(beatmapSet.Metadata.TitleUnicode, beatmapSet.Metadata.Title);
|
||||
artistText.Text = new RomanisableString(beatmapSet.Metadata.ArtistUnicode, beatmapSet.Metadata.Artist);
|
||||
updateButton.BeatmapSet = beatmapSet;
|
||||
statusPill.Status = beatmapSet.Status;
|
||||
difficultiesDisplay.BeatmapSet = beatmapSet;
|
||||
}
|
||||
|
||||
protected override void FreeAfterUse()
|
||||
{
|
||||
base.FreeAfterUse();
|
||||
|
||||
background.Beatmap = null;
|
||||
updateButton.BeatmapSet = null;
|
||||
difficultiesDisplay.BeatmapSet = null;
|
||||
}
|
||||
}
|
||||
}
|
281
osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs
Normal file
281
osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs
Normal file
@ -0,0 +1,281 @@
|
||||
// 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.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
public partial class PanelBeatmapStandalone : PanelBase
|
||||
{
|
||||
public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT * 1.6f;
|
||||
|
||||
[Resolved]
|
||||
private IBindable<RulesetInfo> ruleset { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IBindable<IReadOnlyList<Mod>> mods { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private BeatmapManager beatmaps { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private BeatmapDifficultyCache difficultyCache { get; set; } = null!;
|
||||
|
||||
private IBindable<StarDifficulty?>? starDifficultyBindable;
|
||||
private CancellationTokenSource? starDifficultyCancellationSource;
|
||||
|
||||
private BeatmapSetPanelBackground background = null!;
|
||||
|
||||
private OsuSpriteText titleText = null!;
|
||||
private OsuSpriteText artistText = null!;
|
||||
private UpdateBeatmapSetButton updateButton = null!;
|
||||
private BeatmapSetOnlineStatusPill statusPill = null!;
|
||||
|
||||
private ConstrainedIconContainer difficultyIcon = null!;
|
||||
private FillFlowContainer difficultyLine = null!;
|
||||
private StarRatingDisplay difficultyStarRating = null!;
|
||||
private TopLocalRank difficultyRank = null!;
|
||||
private OsuSpriteText difficultyKeyCountText = null!;
|
||||
private OsuSpriteText difficultyName = null!;
|
||||
private OsuSpriteText difficultyAuthor = null!;
|
||||
|
||||
public PanelBeatmapStandalone()
|
||||
{
|
||||
PanelXOffset = 20;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Height = HEIGHT;
|
||||
|
||||
Icon = difficultyIcon = new ConstrainedIconContainer
|
||||
{
|
||||
Size = new Vector2(20),
|
||||
Margin = new MarginPadding { Horizontal = 5f },
|
||||
Colour = colourProvider.Background5,
|
||||
};
|
||||
|
||||
Background = background = new BeatmapSetPanelBackground
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
};
|
||||
|
||||
Content.Child = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Padding = new MarginPadding { Top = 7.5f, Left = 15, Bottom = 5 },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
titleText = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 22, italics: true),
|
||||
Shadow = true,
|
||||
},
|
||||
artistText = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 17, italics: true),
|
||||
Shadow = true,
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
Direction = FillDirection.Horizontal,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Margin = new MarginPadding { Top = 5f },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
updateButton = new UpdateBeatmapSetButton
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Margin = new MarginPadding { Right = 5f, Top = -2f },
|
||||
},
|
||||
statusPill = new BeatmapSetOnlineStatusPill
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
TextSize = 11,
|
||||
TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 },
|
||||
Margin = new MarginPadding { Right = 5f },
|
||||
},
|
||||
difficultyLine = new FillFlowContainer
|
||||
{
|
||||
Direction = FillDirection.Horizontal,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
difficultyStarRating = new StarRatingDisplay(default, StarRatingDisplaySize.Small)
|
||||
{
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Scale = new Vector2(8f / 9f),
|
||||
Margin = new MarginPadding { Right = 5f },
|
||||
},
|
||||
difficultyRank = new TopLocalRank
|
||||
{
|
||||
Scale = new Vector2(8f / 11),
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Margin = new MarginPadding { Right = 5f },
|
||||
},
|
||||
difficultyKeyCountText = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 18, weight: FontWeight.SemiBold),
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Alpha = 0,
|
||||
Margin = new MarginPadding { Bottom = 2f },
|
||||
},
|
||||
difficultyName = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 18, weight: FontWeight.SemiBold),
|
||||
Origin = Anchor.BottomLeft,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Margin = new MarginPadding { Right = 5f, Bottom = 2f },
|
||||
},
|
||||
difficultyAuthor = new OsuSpriteText
|
||||
{
|
||||
Colour = colourProvider.Content2,
|
||||
Font = OsuFont.GetFont(weight: FontWeight.SemiBold),
|
||||
Origin = Anchor.BottomLeft,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Margin = new MarginPadding { Right = 5f, Bottom = 2f },
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
ruleset.BindValueChanged(_ =>
|
||||
{
|
||||
computeStarRating();
|
||||
updateKeyCount();
|
||||
});
|
||||
|
||||
mods.BindValueChanged(_ =>
|
||||
{
|
||||
computeStarRating();
|
||||
updateKeyCount();
|
||||
}, true);
|
||||
|
||||
Selected.BindValueChanged(s => Expanded.Value = s.NewValue, true);
|
||||
}
|
||||
|
||||
protected override void PrepareForUse()
|
||||
{
|
||||
base.PrepareForUse();
|
||||
|
||||
Debug.Assert(Item != null);
|
||||
|
||||
var beatmap = (BeatmapInfo)Item.Model;
|
||||
var beatmapSet = beatmap.BeatmapSet!;
|
||||
|
||||
// Choice of background image matches BSS implementation (always uses the lowest `beatmap_id` from the set).
|
||||
background.Beatmap = beatmaps.GetWorkingBeatmap(beatmapSet.Beatmaps.MinBy(b => b.OnlineID));
|
||||
|
||||
titleText.Text = new RomanisableString(beatmapSet.Metadata.TitleUnicode, beatmapSet.Metadata.Title);
|
||||
artistText.Text = new RomanisableString(beatmapSet.Metadata.ArtistUnicode, beatmapSet.Metadata.Artist);
|
||||
updateButton.BeatmapSet = beatmapSet;
|
||||
statusPill.Status = beatmapSet.Status;
|
||||
|
||||
difficultyIcon.Icon = beatmap.Ruleset.CreateInstance().CreateIcon();
|
||||
difficultyIcon.Show();
|
||||
|
||||
difficultyRank.Beatmap = beatmap;
|
||||
difficultyName.Text = beatmap.DifficultyName;
|
||||
difficultyAuthor.Text = BeatmapsetsStrings.ShowDetailsMappedBy(beatmap.Metadata.Author.Username);
|
||||
difficultyLine.Show();
|
||||
|
||||
computeStarRating();
|
||||
}
|
||||
|
||||
protected override void FreeAfterUse()
|
||||
{
|
||||
base.FreeAfterUse();
|
||||
|
||||
background.Beatmap = null;
|
||||
updateButton.BeatmapSet = null;
|
||||
difficultyRank.Beatmap = null;
|
||||
starDifficultyBindable = null;
|
||||
}
|
||||
|
||||
private void computeStarRating()
|
||||
{
|
||||
starDifficultyCancellationSource?.Cancel();
|
||||
starDifficultyCancellationSource = new CancellationTokenSource();
|
||||
|
||||
if (Item == null)
|
||||
return;
|
||||
|
||||
var beatmap = (BeatmapInfo)Item.Model;
|
||||
|
||||
starDifficultyBindable = difficultyCache.GetBindableDifficulty(beatmap, starDifficultyCancellationSource.Token);
|
||||
starDifficultyBindable.BindValueChanged(_ => updateDisplay(), true);
|
||||
}
|
||||
|
||||
private void updateKeyCount()
|
||||
{
|
||||
if (Item == null)
|
||||
return;
|
||||
|
||||
var beatmap = (BeatmapInfo)Item.Model;
|
||||
|
||||
if (ruleset.Value.OnlineID == 3)
|
||||
{
|
||||
// Account for mania differences locally for now.
|
||||
// Eventually this should be handled in a more modular way, allowing rulesets to add more information to the panel.
|
||||
ILegacyRuleset legacyRuleset = (ILegacyRuleset)ruleset.Value.CreateInstance();
|
||||
int keyCount = legacyRuleset.GetKeyCount(beatmap, mods.Value);
|
||||
|
||||
difficultyKeyCountText.Alpha = 1;
|
||||
difficultyKeyCountText.Text = $"[{keyCount}K] ";
|
||||
}
|
||||
else
|
||||
difficultyKeyCountText.Alpha = 0;
|
||||
}
|
||||
|
||||
private void updateDisplay()
|
||||
{
|
||||
const float duration = 500;
|
||||
|
||||
var starDifficulty = starDifficultyBindable?.Value ?? default;
|
||||
|
||||
AccentColour = colours.ForStarDifficulty(starDifficulty.Stars);
|
||||
difficultyIcon.FadeColour(starDifficulty.Stars > 6.5f ? colours.Orange1 : colourProvider.Background5, duration, Easing.OutQuint);
|
||||
difficultyStarRating.Current.Value = starDifficulty;
|
||||
}
|
||||
}
|
||||
}
|
108
osu.Game/Screens/SelectV2/PanelGroup.cs
Normal file
108
osu.Game/Screens/SelectV2/PanelGroup.cs
Normal file
@ -0,0 +1,108 @@
|
||||
// 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.Diagnostics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
public partial class PanelGroup : PanelBase
|
||||
{
|
||||
public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT;
|
||||
|
||||
private Drawable chevronIcon = null!;
|
||||
private OsuSpriteText titleText = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
Height = HEIGHT;
|
||||
|
||||
Icon = chevronIcon = new SpriteIcon
|
||||
{
|
||||
AlwaysPresent = true,
|
||||
Icon = FontAwesome.Solid.ChevronDown,
|
||||
Size = new Vector2(12),
|
||||
Margin = new MarginPadding { Horizontal = 5f },
|
||||
X = 2f,
|
||||
Colour = colourProvider.Background3,
|
||||
};
|
||||
Background = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Dark1,
|
||||
};
|
||||
AccentColour = colourProvider.Highlight1;
|
||||
Content.Children = new Drawable[]
|
||||
{
|
||||
titleText = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
X = 10f,
|
||||
},
|
||||
new CircularContainer
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
Size = new Vector2(50f, 14f),
|
||||
Margin = new MarginPadding { Right = 20f },
|
||||
Masking = true,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Black.Opacity(0.7f),
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Font = OsuFont.Torus.With(size: 14.4f, weight: FontWeight.Bold),
|
||||
// TODO: requires Carousel/CarouselItem-side implementation
|
||||
Text = "43",
|
||||
UseFullGlyphHeight = false,
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Expanded.BindValueChanged(_ => onExpanded(), true);
|
||||
}
|
||||
|
||||
private void onExpanded()
|
||||
{
|
||||
const float duration = 500;
|
||||
|
||||
chevronIcon.ResizeWidthTo(Expanded.Value ? 12f : 0f, duration, Easing.OutQuint);
|
||||
chevronIcon.FadeTo(Expanded.Value ? 1f : 0f, duration, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override void PrepareForUse()
|
||||
{
|
||||
base.PrepareForUse();
|
||||
|
||||
Debug.Assert(Item != null);
|
||||
|
||||
GroupDefinition group = (GroupDefinition)Item.Model;
|
||||
|
||||
titleText.Text = group.Title;
|
||||
}
|
||||
}
|
||||
}
|
142
osu.Game/Screens/SelectV2/PanelGroupStarDifficulty.cs
Normal file
142
osu.Game/Screens/SelectV2/PanelGroupStarDifficulty.cs
Normal file
@ -0,0 +1,142 @@
|
||||
// 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.Diagnostics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
public partial class PanelGroupStarDifficulty : PanelBase
|
||||
{
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
private Drawable chevronIcon = null!;
|
||||
private Box contentBackground = null!;
|
||||
private StarRatingDisplay starRatingDisplay = null!;
|
||||
private StarCounter starCounter = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Height = PanelGroup.HEIGHT;
|
||||
|
||||
Icon = chevronIcon = new SpriteIcon
|
||||
{
|
||||
AlwaysPresent = true,
|
||||
Icon = FontAwesome.Solid.ChevronDown,
|
||||
Size = new Vector2(12),
|
||||
Margin = new MarginPadding { Horizontal = 5f },
|
||||
X = 2f,
|
||||
};
|
||||
Background = contentBackground = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Dark1,
|
||||
};
|
||||
AccentColour = colourProvider.Highlight1;
|
||||
Content.Children = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Spacing = new Vector2(10f, 0f),
|
||||
Margin = new MarginPadding { Left = 10f },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
starRatingDisplay = new StarRatingDisplay(default, StarRatingDisplaySize.Small)
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
},
|
||||
starCounter = new StarCounter
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Scale = new Vector2(8f / 20f),
|
||||
},
|
||||
}
|
||||
},
|
||||
new CircularContainer
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
Size = new Vector2(50f, 14f),
|
||||
Margin = new MarginPadding { Right = 20f },
|
||||
Masking = true,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Black.Opacity(0.7f),
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Font = OsuFont.Torus.With(size: 14.4f, weight: FontWeight.Bold),
|
||||
// TODO: requires Carousel/CarouselItem-side implementation
|
||||
Text = "43",
|
||||
UseFullGlyphHeight = false,
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Expanded.BindValueChanged(_ => onExpanded(), true);
|
||||
}
|
||||
|
||||
protected override void PrepareForUse()
|
||||
{
|
||||
base.PrepareForUse();
|
||||
|
||||
Debug.Assert(Item != null);
|
||||
|
||||
int starNumber = (int)((GroupDefinition)Item.Model).Data;
|
||||
|
||||
Color4 colour = starNumber >= 9 ? OsuColour.Gray(0.2f) : colours.ForStarDifficulty(starNumber);
|
||||
Color4 contentColour = starNumber >= 7 ? colours.Orange1 : colourProvider.Background5;
|
||||
|
||||
AccentColour = colour;
|
||||
contentBackground.Colour = colour.Darken(0.3f);
|
||||
|
||||
starRatingDisplay.Current.Value = new StarDifficulty(starNumber, 0);
|
||||
starCounter.Current = starNumber;
|
||||
|
||||
chevronIcon.Colour = contentColour;
|
||||
starCounter.Colour = contentColour;
|
||||
}
|
||||
|
||||
private void onExpanded()
|
||||
{
|
||||
const float duration = 500;
|
||||
|
||||
chevronIcon.ResizeWidthTo(Expanded.Value ? 12f : 0f, duration, Easing.OutQuint);
|
||||
chevronIcon.FadeTo(Expanded.Value ? 1f : 0f, duration, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
@ -39,6 +39,18 @@ namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
AddRangeInternal(new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Bottom = ScreenFooter.HEIGHT },
|
||||
Child = new BeatmapCarousel
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Width = 0.6f,
|
||||
},
|
||||
},
|
||||
modSelectOverlay,
|
||||
});
|
||||
}
|
||||
|
106
osu.Game/Screens/SelectV2/TopLocalRank.cs
Normal file
106
osu.Game/Screens/SelectV2/TopLocalRank.cs
Normal file
@ -0,0 +1,106 @@
|
||||
// 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 System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Models;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Leaderboards;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Scoring;
|
||||
using osuTK;
|
||||
using Realms;
|
||||
|
||||
namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
public partial class TopLocalRank : CompositeDrawable
|
||||
{
|
||||
private BeatmapInfo? beatmap;
|
||||
|
||||
public BeatmapInfo? Beatmap
|
||||
{
|
||||
get => beatmap;
|
||||
set
|
||||
{
|
||||
beatmap = value;
|
||||
|
||||
if (IsLoaded)
|
||||
updateSubscription();
|
||||
}
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private IBindable<RulesetInfo> ruleset { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private RealmAccess realm { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; } = null!;
|
||||
|
||||
private IDisposable? scoreSubscription;
|
||||
|
||||
private readonly UpdateableRank updateable;
|
||||
|
||||
public TopLocalRank(BeatmapInfo? beatmap = null)
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
InternalChild = updateable = new UpdateableRank
|
||||
{
|
||||
Size = new Vector2(40, 20),
|
||||
Alpha = 0,
|
||||
};
|
||||
|
||||
Beatmap = beatmap;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
ruleset.BindValueChanged(_ => updateSubscription(), true);
|
||||
}
|
||||
|
||||
private void updateSubscription()
|
||||
{
|
||||
scoreSubscription?.Dispose();
|
||||
|
||||
if (beatmap == null)
|
||||
return;
|
||||
|
||||
scoreSubscription = realm.RegisterForNotifications(r =>
|
||||
r.All<ScoreInfo>()
|
||||
.Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0"
|
||||
+ $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1"
|
||||
+ $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.Hash)} == {nameof(ScoreInfo.BeatmapHash)}"
|
||||
+ $" && {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $2"
|
||||
+ $" && {nameof(ScoreInfo.DeletePending)} == false", api.LocalUser.Value.Id, beatmap.ID, ruleset.Value.ShortName),
|
||||
localScoresChanged);
|
||||
}
|
||||
|
||||
private void localScoresChanged(IRealmCollection<ScoreInfo> sender, ChangeSet? changes)
|
||||
{
|
||||
// This subscription may fire from changes to linked beatmaps, which we don't care about.
|
||||
// It's currently not possible for a score to be modified after insertion, so we can safely ignore callbacks with only modifications.
|
||||
if (changes?.HasCollectionChanges() == false)
|
||||
return;
|
||||
|
||||
ScoreInfo? topScore = sender.MaxBy(info => (info.TotalScore, -info.Date.UtcDateTime.Ticks));
|
||||
updateable.Rank = topScore?.Rank;
|
||||
updateable.Alpha = topScore != null ? 1 : 0;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
scoreSubscription?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
198
osu.Game/Screens/SelectV2/UpdateBeatmapSetButton.cs
Normal file
198
osu.Game/Screens/SelectV2/UpdateBeatmapSetButton.cs
Normal file
@ -0,0 +1,198 @@
|
||||
// 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.Diagnostics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.Select.Carousel;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
public partial class UpdateBeatmapSetButton : OsuAnimatedButton
|
||||
{
|
||||
private BeatmapSetInfo? beatmapSet;
|
||||
|
||||
public BeatmapSetInfo? BeatmapSet
|
||||
{
|
||||
get => beatmapSet;
|
||||
set
|
||||
{
|
||||
beatmapSet = value;
|
||||
|
||||
if (IsLoaded)
|
||||
beatmapChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private SpriteIcon icon = null!;
|
||||
private Box progressFill = null!;
|
||||
|
||||
[Resolved]
|
||||
private BeatmapModelDownloader beatmapDownloader { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private LoginOverlay? loginOverlay { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IDialogOverlay? dialogOverlay { get; set; }
|
||||
|
||||
public UpdateBeatmapSetButton()
|
||||
{
|
||||
Size = new Vector2(75f, 22f);
|
||||
}
|
||||
|
||||
private Bindable<bool> preferNoVideo = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager config)
|
||||
{
|
||||
const float icon_size = 14;
|
||||
|
||||
preferNoVideo = config.GetBindable<bool>(OsuSetting.PreferNoVideo);
|
||||
|
||||
Content.Anchor = Anchor.Centre;
|
||||
Content.Origin = Anchor.Centre;
|
||||
Content.Shear = new Vector2(OsuGame.SHEAR, 0);
|
||||
|
||||
Content.AddRange(new Drawable[]
|
||||
{
|
||||
progressFill = new Box
|
||||
{
|
||||
Colour = Color4.White,
|
||||
Alpha = 0.2f,
|
||||
Blending = BlendingParameters.Additive,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Width = 0,
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
Padding = new MarginPadding { Horizontal = 5, Vertical = 3 },
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(4),
|
||||
Shear = new Vector2(-OsuGame.SHEAR, 0),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
Size = new Vector2(icon_size),
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
icon = new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Icon = FontAwesome.Solid.SyncAlt,
|
||||
Size = new Vector2(icon_size),
|
||||
},
|
||||
}
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Font = OsuFont.Default.With(weight: FontWeight.Bold),
|
||||
Text = "Update",
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
Action = performUpdate;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
beatmapChanged();
|
||||
}
|
||||
|
||||
private void beatmapChanged()
|
||||
{
|
||||
Alpha = beatmapSet?.AllBeatmapsUpToDate == false ? 1 : 0;
|
||||
icon.Spin(4000, RotationDirection.Clockwise);
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
icon.Spin(400, RotationDirection.Clockwise);
|
||||
return base.OnHover(e);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
icon.Spin(4000, RotationDirection.Clockwise);
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
|
||||
private bool updateConfirmed;
|
||||
|
||||
private void performUpdate()
|
||||
{
|
||||
Debug.Assert(beatmapSet != null);
|
||||
|
||||
if (!api.IsLoggedIn)
|
||||
{
|
||||
loginOverlay?.Show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (dialogOverlay != null && beatmapSet.Status == BeatmapOnlineStatus.LocallyModified && !updateConfirmed)
|
||||
{
|
||||
dialogOverlay.Push(new UpdateLocalConfirmationDialog(() =>
|
||||
{
|
||||
updateConfirmed = true;
|
||||
performUpdate();
|
||||
}));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
updateConfirmed = false;
|
||||
|
||||
beatmapDownloader.DownloadAsUpdate(beatmapSet, preferNoVideo.Value);
|
||||
attachExistingDownload();
|
||||
}
|
||||
|
||||
private void attachExistingDownload()
|
||||
{
|
||||
Debug.Assert(beatmapSet != null);
|
||||
var download = beatmapDownloader.GetExistingDownload(beatmapSet);
|
||||
|
||||
if (download != null)
|
||||
{
|
||||
Enabled.Value = false;
|
||||
TooltipText = string.Empty;
|
||||
|
||||
download.DownloadProgressed += progress => progressFill.ResizeWidthTo(progress, 100, Easing.OutQuint);
|
||||
download.Failure += _ => attachExistingDownload();
|
||||
}
|
||||
else
|
||||
{
|
||||
Enabled.Value = true;
|
||||
TooltipText = "Update beatmap with online changes";
|
||||
|
||||
progressFill.ResizeWidthTo(0, 100, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -228,7 +228,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay
|
||||
{
|
||||
Name = "Collective Wangs",
|
||||
ShortName = "WANG",
|
||||
FlagUrl = "https://assets.ppy.sh/teams/logo/1/wanglogo.jpg",
|
||||
FlagUrl = "https://assets.ppy.sh/teams/flag/1/wanglogo.jpg",
|
||||
}
|
||||
: null,
|
||||
})
|
||||
|
@ -5,6 +5,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Localisation;
|
||||
@ -75,10 +76,18 @@ namespace osu.Game.Users.Drawables
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new HoverClickSounds(),
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Colour4.FromHex("333"),
|
||||
},
|
||||
new Sprite
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Texture = textures.Get(team.FlagUrl)
|
||||
Texture = textures.Get(team.FlagUrl),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
FillMode = FillMode.Fit,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -22,12 +22,12 @@
|
||||
<PackageReference Include="DiffPlex" Version="1.7.2" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.72" />
|
||||
<PackageReference Include="Humanizer" Version="2.14.1" />
|
||||
<PackageReference Include="MessagePack" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.0" />
|
||||
<PackageReference Include="MessagePack" Version="3.1.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="9.0.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="9.0.2" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="9.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.2" />
|
||||
<PackageReference Include="Microsoft.Toolkit.HighPerformance" Version="7.1.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="ppy.LocalisationAnalyser" Version="2024.802.0">
|
||||
@ -35,11 +35,11 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Realm" Version="20.1.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2025.204.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2025.129.0" />
|
||||
<PackageReference Include="Sentry" Version="5.0.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2025.225.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2025.217.0" />
|
||||
<PackageReference Include="Sentry" Version="5.1.1" />
|
||||
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
|
||||
<PackageReference Include="SharpCompress" Version="0.38.0" />
|
||||
<PackageReference Include="SharpCompress" Version="0.39.0" />
|
||||
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.10" />
|
||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||
|
@ -17,6 +17,6 @@
|
||||
<MtouchInterpreter>-all</MtouchInterpreter>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2025.204.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2025.225.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -23,7 +23,7 @@ namespace osu.iOS
|
||||
|
||||
public override bool HideUnlicensedContent => true;
|
||||
|
||||
protected override Vector2 ScalingContainerTargetDrawSize => new Vector2(1024, 1024 * DrawHeight / DrawWidth);
|
||||
public override Vector2 ScalingContainerTargetDrawSize => new Vector2(1024, 1024 * DrawHeight / DrawWidth);
|
||||
|
||||
public OsuGameIOS(AppDelegate appDelegate)
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user