mirror of
https://github.com/ppy/osu.git
synced 2026-06-02 03:59:54 +08:00
Merge pull request #31148 from peppy/editor-right-click-as-people-expect
Add back right-click-for-new-combo and right-click-delete when in object placement mode
This commit is contained in:
@@ -30,6 +30,7 @@ using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Edit.Components.TernaryButtons;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
@@ -351,6 +352,35 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
{
|
||||
// Why is this logic here and not in `OsuSelectionHandler`?
|
||||
// Because we only want to handle this toggle after all other right-click handling completes.
|
||||
//
|
||||
// Consider that input is handled from the most nested child first:
|
||||
//
|
||||
// ComposeScreen
|
||||
// |- OsuContextMenuContainer // right click for context
|
||||
// |- TimelineBlueprintContainer
|
||||
// |- TimelineSelectionHandler
|
||||
// |- (Osu)HitObjectComposer // right click for toggle new combo
|
||||
// |- (Osu)EditorBlueprintContainer // right click for select
|
||||
// |- (Osu)EditorSelectionHandler // right click for delete
|
||||
if (e.Button == MouseButton.Right)
|
||||
{
|
||||
var osuSelectionHandler = (OsuSelectionHandler)BlueprintContainer.SelectionHandler;
|
||||
|
||||
if (!osuSelectionHandler.SelectedItems.Any())
|
||||
{
|
||||
osuSelectionHandler.SelectionNewComboState.Value =
|
||||
osuSelectionHandler.SelectionNewComboState.Value == TernaryState.False ? TernaryState.True : TernaryState.False;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return base.OnMouseDown(e);
|
||||
}
|
||||
|
||||
protected override bool OnKeyDown(KeyDownEvent e)
|
||||
{
|
||||
if (e.Repeat)
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||
using osu.Game.Tests.Visual;
|
||||
@@ -31,7 +29,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Editor
|
||||
AddStep("hover over first hit", () => InputManager.MoveMouseTo(Editor.ChildrenOfType<DrawableHit>().ElementAt(1)));
|
||||
AddStep("hover over second hit", () => InputManager.MoveMouseTo(Editor.ChildrenOfType<DrawableHit>().ElementAt(0)));
|
||||
AddStep("right click", () => InputManager.Click(MouseButton.Right));
|
||||
AddUntilStep("context menu open", () => Editor.ChildrenOfType<OsuContextMenu>().Any(menu => menu.State == MenuState.Open));
|
||||
AddUntilStep("second hit deleted", () => Editor.ChildrenOfType<DrawableHit>().Count(), () => Is.EqualTo(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
@@ -14,8 +15,10 @@ using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Edit.Components.TernaryButtons;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Editing
|
||||
@@ -58,19 +61,63 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestContextMenu()
|
||||
public void TestRightClickDuringEmptyPlacementTogglesNewCombo()
|
||||
{
|
||||
AddStep("select circle placement tool", () => InputManager.Key(Key.Number2));
|
||||
AddStep("move mouse to center of playfield", () => InputManager.MoveMouseTo(this.ChildrenOfType<Playfield>().Single()));
|
||||
AddStep("place circle", () => InputManager.Click(MouseButton.Left));
|
||||
AddAssert("one circle added", () => EditorBeatmap.HitObjects, () => Has.One.Items);
|
||||
|
||||
AddStep("move mouse away from placed circle", () => InputManager.MoveMouseTo(this.ChildrenOfType<Playfield>().Single().ScreenSpaceDrawQuad.TopLeft + Vector2.One));
|
||||
|
||||
AddAssert("new combo false", () => this.ChildrenOfType<NewComboTernaryButton>().Single().Current.Value, () => Is.EqualTo(TernaryState.False));
|
||||
AddStep("click right mouse", () => InputManager.Click(MouseButton.Right));
|
||||
AddAssert("new combo true", () => this.ChildrenOfType<NewComboTernaryButton>().Single().Current.Value, () => Is.EqualTo(TernaryState.True));
|
||||
AddAssert("context menu not visible", () => !Editor.ChildrenOfType<OsuContextMenu>().Any(c => c.IsPresent));
|
||||
|
||||
AddStep("click right mouse", () => InputManager.Click(MouseButton.Right));
|
||||
AddAssert("new combo false", () => this.ChildrenOfType<NewComboTernaryButton>().Single().Current.Value, () => Is.EqualTo(TernaryState.False));
|
||||
AddAssert("context menu not visible", () => !Editor.ChildrenOfType<OsuContextMenu>().Any(c => c.IsPresent));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRightClickDuringPlacementDeletes()
|
||||
{
|
||||
AddStep("select circle placement tool", () => InputManager.Key(Key.Number2));
|
||||
AddStep("move mouse to center of playfield", () => InputManager.MoveMouseTo(this.ChildrenOfType<Playfield>().Single()));
|
||||
AddStep("place circle", () => InputManager.Click(MouseButton.Left));
|
||||
AddAssert("one circle added", () => EditorBeatmap.HitObjects, () => Has.One.Items);
|
||||
|
||||
AddStep("click right mouse", () => InputManager.Click(MouseButton.Right));
|
||||
|
||||
AddAssert("circle removed", () => EditorBeatmap.HitObjects, () => Has.Exactly(0).Items);
|
||||
AddAssert("circle not selected", () => EditorBeatmap.SelectedHitObjects, () => Has.Exactly(0).Items);
|
||||
AddAssert("context menu not visible", () => !Editor.ChildrenOfType<OsuContextMenu>().Any(c => c.IsPresent));
|
||||
AddAssert("new combo false", () => this.ChildrenOfType<NewComboTernaryButton>().Single().Current.Value, () => Is.EqualTo(TernaryState.False));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRightClickDuringSelectionShowsContextMenu()
|
||||
{
|
||||
AddStep("select circle placement tool", () => InputManager.Key(Key.Number2));
|
||||
AddStep("move mouse to center of playfield", () => InputManager.MoveMouseTo(this.ChildrenOfType<Playfield>().Single()));
|
||||
AddStep("place circle", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddAssert("one circle added", () => EditorBeatmap.HitObjects, () => Has.One.Items);
|
||||
AddStep("delete with right mouse", () =>
|
||||
{
|
||||
InputManager.Click(MouseButton.Right);
|
||||
});
|
||||
AddAssert("circle not removed", () => EditorBeatmap.HitObjects, () => Has.One.Items);
|
||||
// ensure the circle we're selecting is not a new combo so we can assert
|
||||
// new combo doesn't happen to get toggled by right click.
|
||||
AddStep("seek forward", () => EditorClock.Seek(1000));
|
||||
AddStep("place second circle", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddAssert("two circles added", () => EditorBeatmap.HitObjects, () => Has.Exactly(2).Items);
|
||||
AddAssert("context menu not visible", () => !Editor.ChildrenOfType<OsuContextMenu>().Any(c => c.IsPresent));
|
||||
|
||||
AddStep("select selection tool", () => InputManager.Key(Key.Number1));
|
||||
AddStep("click right mouse", () => InputManager.Click(MouseButton.Right));
|
||||
|
||||
AddAssert("circle not removed", () => EditorBeatmap.HitObjects, () => Has.Exactly(2).Items);
|
||||
AddAssert("circle selected", () => EditorBeatmap.SelectedHitObjects, () => Has.One.Items);
|
||||
AddAssert("context menu visible", () => Editor.ChildrenOfType<OsuContextMenu>().Any(c => c.IsPresent));
|
||||
AddAssert("new combo false", () => this.ChildrenOfType<NewComboTernaryButton>().Single().Current.Value, () => Is.EqualTo(TernaryState.False));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -115,19 +115,19 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
{
|
||||
bool selectionPerformed = performMouseDownActions(e);
|
||||
bool handled = performMouseDownActions(e);
|
||||
bool movementPossible = prepareSelectionMovement(e);
|
||||
|
||||
// check if selection has occurred
|
||||
if (selectionPerformed)
|
||||
if (SelectedItems.Any())
|
||||
{
|
||||
// only unmodified right click should show context menu
|
||||
// if there is a selection and there are no modifiers pressed, don't block so the context menu still shows.
|
||||
bool shouldShowContextMenu = e.Button == MouseButton.Right && !e.ShiftPressed && !e.AltPressed && !e.SuperPressed;
|
||||
|
||||
// stop propagation if not showing context menu
|
||||
return !shouldShowContextMenu;
|
||||
}
|
||||
|
||||
if (handled)
|
||||
return true;
|
||||
|
||||
// even if a selection didn't occur, a drag event may still move the selection.
|
||||
return e.Button == MouseButton.Left && movementPossible;
|
||||
}
|
||||
|
||||
@@ -387,6 +387,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
|
||||
currentTool = value;
|
||||
|
||||
SelectionHandler.RightClickAlwaysQuickDeletes = currentTool is not SelectTool;
|
||||
|
||||
// As per stable editor, when changing tools, we should forcefully commit any pending placement.
|
||||
CommitIfPlacementActive();
|
||||
}
|
||||
|
||||
@@ -10,16 +10,23 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Compose.Components
|
||||
{
|
||||
public partial class EditorSelectionHandler : SelectionHandler<HitObject>
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether right click should delete even when shift is not held.
|
||||
/// </summary>
|
||||
public bool RightClickAlwaysQuickDeletes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A special bank name that is only used in the editor UI.
|
||||
/// When selected and in placement mode, the bank of the last hit object will always be used.
|
||||
@@ -40,6 +47,14 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
SelectedItems.CollectionChanged += onSelectedItemsChanged;
|
||||
}
|
||||
|
||||
protected override bool ShouldQuickDelete(MouseButtonEvent e)
|
||||
{
|
||||
if (RightClickAlwaysQuickDeletes && e.Button == MouseButton.Right)
|
||||
return true;
|
||||
|
||||
return base.ShouldQuickDelete(e);
|
||||
}
|
||||
|
||||
protected override void DeleteItems(IEnumerable<HitObject> items) => EditorBeatmap.RemoveRange(items);
|
||||
|
||||
#region Selection State
|
||||
@@ -293,7 +308,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
|
||||
foreach ((string bankName, var bindable) in SelectionAdditionBankStates)
|
||||
{
|
||||
bindable.Value = GetStateFromSelection(samplesInSelection.SelectMany(s => s).Where(o => o.Name != HitSampleInfo.HIT_NORMAL), h => (bankName != HIT_BANK_AUTO && h.Bank == bankName && !h.EditorAutoBank) || (bankName == HIT_BANK_AUTO && h.EditorAutoBank));
|
||||
bindable.Value = GetStateFromSelection(samplesInSelection.SelectMany(s => s).Where(o => o.Name != HitSampleInfo.HIT_NORMAL),
|
||||
h => (bankName != HIT_BANK_AUTO && h.Bank == bankName && !h.EditorAutoBank) || (bankName == HIT_BANK_AUTO && h.EditorAutoBank));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,14 +394,21 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
return;
|
||||
|
||||
string normalBank = h.Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)?.Bank ?? HitSampleInfo.BANK_SOFT;
|
||||
h.Samples = h.Samples.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();
|
||||
h.Samples = h.Samples.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();
|
||||
|
||||
if (h is IHasRepeats hasRepeats)
|
||||
{
|
||||
for (int i = 0; i < hasRepeats.NodeSamples.Count; ++i)
|
||||
{
|
||||
normalBank = hasRepeats.NodeSamples[i].FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)?.Bank ?? HitSampleInfo.BANK_SOFT;
|
||||
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();
|
||||
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();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -255,15 +255,17 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
selectedBlueprints.Remove(blueprint);
|
||||
}
|
||||
|
||||
protected virtual bool ShouldQuickDelete(MouseButtonEvent e) => e.Button == MouseButton.Middle || (e.ShiftPressed && e.Button == MouseButton.Right);
|
||||
|
||||
/// <summary>
|
||||
/// Handle a blueprint requesting selection.
|
||||
/// </summary>
|
||||
/// <param name="blueprint">The blueprint.</param>
|
||||
/// <param name="e">The mouse event responsible for selection.</param>
|
||||
/// <returns>Whether a selection was performed.</returns>
|
||||
/// <returns>Whether an action was performed.</returns>
|
||||
internal virtual bool MouseDownSelectionRequested(SelectionBlueprint<T> blueprint, MouseButtonEvent e)
|
||||
{
|
||||
if (e.Button == MouseButton.Middle || (e.ShiftPressed && e.Button == MouseButton.Right))
|
||||
if (ShouldQuickDelete(e))
|
||||
{
|
||||
handleQuickDeletion(blueprint);
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user