1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-12 15:22:55 +08:00

Merge branch 'master' into master

This commit is contained in:
Bartłomiej Dach 2023-05-14 19:45:31 +02:00 committed by GitHub
commit a6ddb107cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 200 additions and 22 deletions

View File

@ -11,7 +11,7 @@
<AndroidManifestMerger>manifestmerger.jar</AndroidManifestMerger> <AndroidManifestMerger>manifestmerger.jar</AndroidManifestMerger>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.510.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2023.513.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<AndroidManifestOverlay Include="$(MSBuildThisFileDirectory)osu.Android\Properties\AndroidManifestOverlay.xml" /> <AndroidManifestOverlay Include="$(MSBuildThisFileDirectory)osu.Android\Properties\AndroidManifestOverlay.xml" />

View File

@ -17,6 +17,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
private double placementStartTime; private double placementStartTime;
private double placementEndTime; private double placementEndTime;
protected override bool IsValidForPlacement => HitObject.Duration > 0;
public BananaShowerPlacementBlueprint() public BananaShowerPlacementBlueprint()
{ {
InternalChild = outline = new TimeSpanOutline(); InternalChild = outline = new TimeSpanOutline();
@ -49,7 +51,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
case PlacementState.Active: case PlacementState.Active:
if (e.Button != MouseButton.Right) break; if (e.Button != MouseButton.Right) break;
EndPlacement(HitObject.Duration > 0); EndPlacement(true);
return true; return true;
} }

View File

@ -24,6 +24,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
private InputManager inputManager = null!; private InputManager inputManager = null!;
protected override bool IsValidForPlacement => HitObject.Duration > 0;
public JuiceStreamPlacementBlueprint() public JuiceStreamPlacementBlueprint()
{ {
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
@ -70,7 +72,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
return true; return true;
case MouseButton.Right: case MouseButton.Right:
EndPlacement(HitObject.Duration > 0); EndPlacement(true);
return true; return true;
} }

View File

@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
[Resolved] [Resolved]
private IScrollingInfo scrollingInfo { get; set; } private IScrollingInfo scrollingInfo { get; set; }
protected override bool IsValidForPlacement => HitObject.Duration > 0;
public HoldNotePlacementBlueprint() public HoldNotePlacementBlueprint()
: base(new HoldNote()) : base(new HoldNote())
{ {
@ -75,7 +77,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
return; return;
base.OnMouseUp(e); base.OnMouseUp(e);
EndPlacement(HitObject.Duration > 0); EndPlacement(true);
} }
private double originalStartTime; private double originalStartTime;

View File

@ -41,6 +41,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
[Resolved(CanBeNull = true)] [Resolved(CanBeNull = true)]
private IDistanceSnapProvider snapProvider { get; set; } private IDistanceSnapProvider snapProvider { get; set; }
protected override bool IsValidForPlacement => HitObject.Path.HasValidLength;
public SliderPlacementBlueprint() public SliderPlacementBlueprint()
: base(new Slider()) : base(new Slider())
{ {
@ -150,7 +152,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
private void endCurve() private void endCurve()
{ {
updateSlider(); updateSlider();
EndPlacement(HitObject.Path.HasValidLength); EndPlacement(true);
} }
protected override void Update() protected override void Update()

View File

@ -98,6 +98,7 @@ namespace osu.Game.Rulesets.Osu.Mods
ComboOffset = original.ComboOffset; ComboOffset = original.ComboOffset;
LegacyLastTickOffset = original.LegacyLastTickOffset; LegacyLastTickOffset = original.LegacyLastTickOffset;
TickDistanceMultiplier = original.TickDistanceMultiplier; TickDistanceMultiplier = original.TickDistanceMultiplier;
SliderVelocity = original.SliderVelocity;
} }
protected override void CreateNestedHitObjects(CancellationToken cancellationToken) protected override void CreateNestedHitObjects(CancellationToken cancellationToken)

View File

@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
private readonly IHasDuration spanPlacementObject; private readonly IHasDuration spanPlacementObject;
protected override bool IsValidForPlacement => spanPlacementObject.Duration > 0;
public TaikoSpanPlacementBlueprint(HitObject hitObject) public TaikoSpanPlacementBlueprint(HitObject hitObject)
: base(hitObject) : base(hitObject)
{ {
@ -73,7 +75,7 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
return; return;
base.OnMouseUp(e); base.OnMouseUp(e);
EndPlacement(spanPlacementObject.Duration > 0); EndPlacement(true);
} }
public override void UpdateTimeAndPosition(SnapResult result) public override void UpdateTimeAndPosition(SnapResult result)

View 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.Linq;
using NUnit.Framework;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Input.Bindings;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Edit.Compose.Components;
using osu.Game.Tests.Beatmaps;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Editing
{
public partial class TestScenePlacementBlueprint : EditorTestScene
{
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
private GlobalActionContainer globalActionContainer => this.ChildrenOfType<GlobalActionContainer>().Single();
[Test]
public void TestCommitPlacementViaGlobalAction()
{
Playfield playfield = null!;
AddStep("select slider placement tool", () => InputManager.Key(Key.Number3));
AddStep("move mouse to top left of playfield", () =>
{
playfield = this.ChildrenOfType<Playfield>().Single();
var location = (3 * playfield.ScreenSpaceDrawQuad.TopLeft + playfield.ScreenSpaceDrawQuad.BottomRight) / 4;
InputManager.MoveMouseTo(location);
});
AddStep("begin placement", () => InputManager.Click(MouseButton.Left));
AddStep("move mouse to bottom right of playfield", () =>
{
var location = (playfield.ScreenSpaceDrawQuad.TopLeft + 3 * playfield.ScreenSpaceDrawQuad.BottomRight) / 4;
InputManager.MoveMouseTo(location);
});
AddStep("confirm via global action", () =>
{
globalActionContainer.TriggerPressed(GlobalAction.Select);
globalActionContainer.TriggerReleased(GlobalAction.Select);
});
AddAssert("slider placed", () => EditorBeatmap.HitObjects.Count, () => Is.EqualTo(1));
}
[Test]
public void TestAbortPlacementViaGlobalAction()
{
Playfield playfield = null!;
AddStep("select slider placement tool", () => InputManager.Key(Key.Number3));
AddStep("move mouse to top left of playfield", () =>
{
playfield = this.ChildrenOfType<Playfield>().Single();
var location = (3 * playfield.ScreenSpaceDrawQuad.TopLeft + playfield.ScreenSpaceDrawQuad.BottomRight) / 4;
InputManager.MoveMouseTo(location);
});
AddStep("begin placement", () => InputManager.Click(MouseButton.Left));
AddStep("move mouse to bottom right of playfield", () =>
{
var location = (playfield.ScreenSpaceDrawQuad.TopLeft + 3 * playfield.ScreenSpaceDrawQuad.BottomRight) / 4;
InputManager.MoveMouseTo(location);
});
AddStep("abort via global action", () =>
{
globalActionContainer.TriggerPressed(GlobalAction.Back);
globalActionContainer.TriggerReleased(GlobalAction.Back);
});
AddAssert("editor is still current", () => Editor.IsCurrentScreen());
AddAssert("slider not placed", () => EditorBeatmap.HitObjects.Count, () => Is.EqualTo(0));
AddAssert("no active placement", () => this.ChildrenOfType<ComposeBlueprintContainer>().Single().CurrentPlacement.PlacementActive,
() => Is.EqualTo(PlacementBlueprint.PlacementState.Waiting));
}
[Test]
public void TestCommitPlacementViaToolChange()
{
Playfield playfield = null!;
AddStep("select slider placement tool", () => InputManager.Key(Key.Number3));
AddStep("move mouse to top left of playfield", () =>
{
playfield = this.ChildrenOfType<Playfield>().Single();
var location = (3 * playfield.ScreenSpaceDrawQuad.TopLeft + playfield.ScreenSpaceDrawQuad.BottomRight) / 4;
InputManager.MoveMouseTo(location);
});
AddStep("begin placement", () => InputManager.Click(MouseButton.Left));
AddStep("move mouse to bottom right of playfield", () =>
{
var location = (playfield.ScreenSpaceDrawQuad.TopLeft + 3 * playfield.ScreenSpaceDrawQuad.BottomRight) / 4;
InputManager.MoveMouseTo(location);
});
AddStep("change tool to circle", () => InputManager.Key(Key.Number2));
AddAssert("slider placed", () => EditorBeatmap.HitObjects.Count, () => Is.EqualTo(1));
}
}
}

View File

@ -3,6 +3,7 @@
#nullable disable #nullable disable
using osu.Framework.Bindables;
using osu.Framework.Input; using osu.Framework.Input;
using osuTK.Input; using osuTK.Input;
@ -10,6 +11,10 @@ namespace osu.Game.Input
{ {
public partial class OsuUserInputManager : UserInputManager public partial class OsuUserInputManager : UserInputManager
{ {
protected override bool AllowRightClickFromLongTouch => !LocalUserPlaying.Value;
public readonly BindableBool LocalUserPlaying = new BindableBool();
internal OsuUserInputManager() internal OsuUserInputManager()
{ {
} }

View File

@ -269,6 +269,13 @@ namespace osu.Game
if (hideToolbar) Toolbar.Hide(); if (hideToolbar) Toolbar.Hide();
} }
protected override UserInputManager CreateUserInputManager()
{
var userInputManager = base.CreateUserInputManager();
(userInputManager as OsuUserInputManager)?.LocalUserPlaying.BindTo(LocalUserPlaying);
return userInputManager;
}
private DependencyContainer dependencies; private DependencyContainer dependencies;
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) =>

View File

@ -3,6 +3,8 @@
#nullable disable #nullable disable
using System.Collections.Generic;
using System.Linq;
using osu.Framework; using osu.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
@ -23,6 +25,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input
{ {
public partial class TabletSettings : SettingsSubsection public partial class TabletSettings : SettingsSubsection
{ {
public override IEnumerable<LocalisableString> FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { "area" });
public TabletAreaSelection AreaSelection { get; private set; } public TabletAreaSelection AreaSelection { get; private set; }
private readonly ITabletHandler tabletHandler; private readonly ITabletHandler tabletHandler;

View File

@ -9,10 +9,12 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Input.Bindings;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Screens.Edit; using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Compose;
@ -24,7 +26,7 @@ namespace osu.Game.Rulesets.Edit
/// <summary> /// <summary>
/// A blueprint which governs the creation of a new <see cref="HitObject"/> to actualisation. /// A blueprint which governs the creation of a new <see cref="HitObject"/> to actualisation.
/// </summary> /// </summary>
public abstract partial class PlacementBlueprint : CompositeDrawable public abstract partial class PlacementBlueprint : CompositeDrawable, IKeyBindingHandler<GlobalAction>
{ {
/// <summary> /// <summary>
/// Whether the <see cref="HitObject"/> is currently mid-placement, but has not necessarily finished being placed. /// Whether the <see cref="HitObject"/> is currently mid-placement, but has not necessarily finished being placed.
@ -47,6 +49,15 @@ namespace osu.Game.Rulesets.Edit
[Resolved] [Resolved]
private IPlacementHandler placementHandler { get; set; } private IPlacementHandler placementHandler { get; set; }
/// <summary>
/// Whether this blueprint is currently in a state that can be committed.
/// </summary>
/// <remarks>
/// Override this with any preconditions that should be double-checked on committing.
/// If <c>false</c> is returned and a commit is attempted, the blueprint will be destroyed instead.
/// </remarks>
protected virtual bool IsValidForPlacement => true;
protected PlacementBlueprint(HitObject hitObject) protected PlacementBlueprint(HitObject hitObject)
{ {
HitObject = hitObject; HitObject = hitObject;
@ -88,7 +99,7 @@ namespace osu.Game.Rulesets.Edit
/// Signals that the placement of <see cref="HitObject"/> has finished. /// Signals that the placement of <see cref="HitObject"/> has finished.
/// This will destroy this <see cref="PlacementBlueprint"/>, and add the HitObject.StartTime to the <see cref="Beatmap"/>. /// This will destroy this <see cref="PlacementBlueprint"/>, and add the HitObject.StartTime to the <see cref="Beatmap"/>.
/// </summary> /// </summary>
/// <param name="commit">Whether the object should be committed.</param> /// <param name="commit">Whether the object should be committed. Note that a commit may fail if <see cref="IsValidForPlacement"/> is <c>false</c>.</param>
public void EndPlacement(bool commit) public void EndPlacement(bool commit)
{ {
switch (PlacementActive) switch (PlacementActive)
@ -102,10 +113,34 @@ namespace osu.Game.Rulesets.Edit
break; break;
} }
placementHandler.EndPlacement(HitObject, commit); placementHandler.EndPlacement(HitObject, IsValidForPlacement && commit);
PlacementActive = PlacementState.Finished; PlacementActive = PlacementState.Finished;
} }
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{
if (PlacementActive == PlacementState.Waiting)
return false;
switch (e.Action)
{
case GlobalAction.Select:
EndPlacement(true);
return true;
case GlobalAction.Back:
EndPlacement(false);
return true;
default:
return false;
}
}
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
{
}
/// <summary> /// <summary>
/// Updates the time and position of this <see cref="PlacementBlueprint"/> based on the provided snap information. /// Updates the time and position of this <see cref="PlacementBlueprint"/> based on the provided snap information.
/// </summary> /// </summary>

View File

@ -28,6 +28,8 @@ namespace osu.Game.Rulesets.UI
public abstract partial class RulesetInputManager<T> : PassThroughInputManager, ICanAttachHUDPieces, IHasReplayHandler, IHasRecordingHandler public abstract partial class RulesetInputManager<T> : PassThroughInputManager, ICanAttachHUDPieces, IHasReplayHandler, IHasRecordingHandler
where T : struct where T : struct
{ {
protected override bool AllowRightClickFromLongTouch => false;
public readonly KeyBindingContainer<T> KeyBindingContainer; public readonly KeyBindingContainer<T> KeyBindingContainer;
[Resolved(CanBeNull = true)] [Resolved(CanBeNull = true)]

View File

@ -317,12 +317,16 @@ namespace osu.Game.Screens.Edit.Compose.Components
} }
} }
private void commitIfPlacementActive()
{
CurrentPlacement?.EndPlacement(CurrentPlacement.PlacementActive == PlacementBlueprint.PlacementState.Active);
removePlacement();
}
private void removePlacement() private void removePlacement()
{ {
if (CurrentPlacement == null) return; CurrentPlacement?.EndPlacement(false);
CurrentPlacement?.Expire();
CurrentPlacement.EndPlacement(false);
CurrentPlacement.Expire();
CurrentPlacement = null; CurrentPlacement = null;
} }
@ -342,7 +346,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
currentTool = value; currentTool = value;
refreshTool(); // As per stable editor, when changing tools, we should forcefully commit any pending placement.
commitIfPlacementActive();
} }
} }
} }

View File

@ -15,6 +15,7 @@ using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Layout;
using osu.Framework.Threading; using osu.Framework.Threading;
namespace osu.Game.Screens.Play namespace osu.Game.Screens.Play
@ -51,7 +52,7 @@ namespace osu.Game.Screens.Play
if (value == values) return; if (value == values) return;
values = value; values = value;
graphNeedsUpdate = true; layout.Invalidate();
} }
} }
@ -71,23 +72,25 @@ namespace osu.Game.Screens.Play
private ScheduledDelegate scheduledCreate; private ScheduledDelegate scheduledCreate;
private bool graphNeedsUpdate; private readonly LayoutValue layout = new LayoutValue(Invalidation.DrawSize | Invalidation.DrawInfo);
private Vector2 previousDrawSize; public SquareGraph()
{
AddLayout(layout);
}
protected override void Update() protected override void Update()
{ {
base.Update(); base.Update();
if (graphNeedsUpdate || (values != null && DrawSize != previousDrawSize)) if (!layout.IsValid)
{ {
columns?.FadeOut(500, Easing.OutQuint).Expire(); columns?.FadeOut(500, Easing.OutQuint).Expire();
scheduledCreate?.Cancel(); scheduledCreate?.Cancel();
scheduledCreate = Scheduler.AddDelayed(RecreateGraph, 500); scheduledCreate = Scheduler.AddDelayed(RecreateGraph, 500);
previousDrawSize = DrawSize; layout.Validate();
graphNeedsUpdate = false;
} }
} }

View File

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

View File

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