1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-27 05:49:56 +08:00

Keep any stateful dropdown menu open on click (#37021)

Right click is a very obfuscated UX which most users won't find. This
makes more sense to the average user (probably).

Caveat: clicking away actually sends clicks to underlying UI. This is
not the case in macOS or windows (locally, same app; globally it still
sends clicks to other windows).

Coming from https://github.com/ppy/osu/discussions/36926.

---------

Co-authored-by: Bartłomiej Dach <dach.bartlomiej@gmail.com>
This commit is contained in:
Dean Herbert
2026-03-21 22:18:02 +09:00
committed by GitHub
Unverified
parent 22b8a8e57d
commit 1016bca280
3 changed files with 8 additions and 66 deletions
@@ -503,27 +503,27 @@ namespace osu.Game.Tests.Visual.Gameplay
InputManager.Click(MouseButton.Left);
});
AddStep("Right-click TopLeft anchor", () =>
AddStep("Click TopLeft anchor", () =>
{
InputManager.MoveMouseTo(getMenuItemByText("TopLeft"));
InputManager.Click(MouseButton.Right);
InputManager.Click(MouseButton.Left);
});
AddAssert("TopLeft item checked", () => (getMenuItemByText("TopLeft").Item as TernaryStateRadioMenuItem)?.State.Value == TernaryState.True);
AddStep("Right-click Centre anchor", () =>
AddStep("Click Centre anchor", () =>
{
InputManager.MoveMouseTo(getMenuItemByText("Centre"));
InputManager.Click(MouseButton.Right);
InputManager.Click(MouseButton.Left);
});
AddAssert("Centre item checked", () => (getMenuItemByText("Centre").Item as TernaryStateRadioMenuItem)?.State.Value == TernaryState.True);
AddAssert("TopLeft item unchecked", () => (getMenuItemByText("TopLeft").Item as TernaryStateRadioMenuItem)?.State.Value == TernaryState.False);
AddStep("Right-click Closest anchor", () =>
AddStep("Click Closest anchor", () =>
{
InputManager.MoveMouseTo(getMenuItemByText("Closest"));
InputManager.Click(MouseButton.Right);
InputManager.Click(MouseButton.Left);
});
AddAssert("Closest item checked", () => (getMenuItemByText("Closest").Item as TernaryStateRadioMenuItem)?.State.Value == TernaryState.True);
@@ -114,51 +114,6 @@ namespace osu.Game.Tests.Visual.UserInterface
=> AddAssert($"state is {expected}", () => state.Value == expected);
}
[Test]
public void TestItemRespondsToRightClick()
{
OsuMenu menu = null;
Bindable<TernaryState> state = new Bindable<TernaryState>(TernaryState.Indeterminate);
AddStep("create menu", () =>
{
state.Value = TernaryState.Indeterminate;
Child = menu = new OsuMenu(Direction.Vertical, true)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Items = new[]
{
new TernaryStateToggleMenuItem("First"),
new TernaryStateToggleMenuItem("Second") { State = { BindTarget = state } },
new TernaryStateToggleMenuItem("Third") { State = { Value = TernaryState.True } },
}
};
});
checkState(TernaryState.Indeterminate);
click();
checkState(TernaryState.True);
click();
checkState(TernaryState.False);
AddStep("change state via bindable", () => state.Value = TernaryState.True);
void click() =>
AddStep("click", () =>
{
InputManager.MoveMouseTo(menu.ScreenSpaceDrawQuad.Centre);
InputManager.Click(MouseButton.Right);
});
void checkState(TernaryState expected)
=> AddAssert($"state is {expected}", () => state.Value == expected);
}
[Test]
public void TestCustomState()
{
@@ -4,9 +4,7 @@
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osuTK;
using osuTK.Input;
namespace osu.Game.Graphics.UserInterface
{
@@ -14,6 +12,8 @@ namespace osu.Game.Graphics.UserInterface
{
protected new StatefulMenuItem Item => (StatefulMenuItem)base.Item;
public override bool CloseMenuOnClick => false;
public DrawableStatefulMenuItem(StatefulMenuItem item)
: base(item)
{
@@ -21,19 +21,6 @@ namespace osu.Game.Graphics.UserInterface
protected override TextContainer CreateTextContainer() => new ToggleTextContainer(Item);
protected override bool OnMouseDown(MouseDownEvent e)
{
// Right mouse button is a special case where we allow actioning without dismissing the menu.
// This is achieved by not calling `Clicked` (as done by the base implementation in OnClick).
if (IsActionable && e.Button == MouseButton.Right)
{
Item.Action.Value?.Invoke();
return true;
}
return false;
}
private partial class ToggleTextContainer : TextContainer
{
private readonly StatefulMenuItem menuItem;