Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 145 additions & 4 deletions osu.Framework.Tests/Visual/UserInterface/TestSceneDropdown.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input;
Expand All @@ -21,6 +22,7 @@
using osu.Framework.Testing;
using osu.Framework.Testing.Input;
using osuTK;
using osuTK.Graphics;
using osuTK.Input;

namespace osu.Framework.Tests.Visual.UserInterface
Expand All @@ -29,16 +31,32 @@ public partial class TestSceneDropdown : ManualInputManagerTestScene
{
private const int items_to_add = 10;

[Test]
public void TestBasic()
[TestCase(false)]
[TestCase(true)]
public void TestToggleOnMouseDown(bool toggleOnMouseDown)
{
AddStep("setup dropdowns", () =>
{
TestDropdown[] dropdowns = createDropdowns(2);
dropdowns.ForEach(dropdown => dropdown.ToggleOnMouseDown = toggleOnMouseDown);
dropdowns[1].AlwaysShowSearchBar = true;
});
}

[Test]
public void TestInsideScrollContainer()
{
TestDropdown[] dropdowns = [];

AddStep("setup dropdowns", () =>
{
dropdowns = createDropdownInScrollableParentScene<TestDropdown>();
});

AddAssert("dropdown in scrollable has ToggleOnMouseDown set to false", () => dropdowns.ElementAt(0).ToggleOnMouseDown == false);
AddAssert("dropdown in non-scrollable has ToggleOnMouseDown set to true", () => dropdowns.ElementAt(1).ToggleOnMouseDown);
}

[Test]
public void TestSelectByUserInteraction()
{
Expand All @@ -64,6 +82,64 @@ public void TestSelectByUserInteraction()
.Item as DropdownMenuItem<TestModel?>)?.Value?.Identifier == "test 2");
}

[Test]
public void TestSelectByUserPressAndRelease()
{
TestDropdown testDropdown = null!;

AddStep("setup dropdown", () =>
{
testDropdown = createDropdown();
testDropdown.ToggleOnMouseDown = true;
});

toggleDropdownViaPress(() => testDropdown);
assertDropdownIsOpen(() => testDropdown);

AddStep("release on item 2", () =>
{
InputManager.MoveMouseTo(testDropdown.Menu.Children[2]);
InputManager.ReleaseButton(MouseButton.Left);
});

assertDropdownIsClosed(() => testDropdown);

AddAssert("item 2 is selected", () => testDropdown.Current.Value?.Equals(testDropdown.Items.ElementAt(2)) == true);
AddAssert("item 2 is selected item", () => testDropdown.SelectedItem.Value?.Identifier == "test 2");
AddAssert("item 2 is visually selected", () => (testDropdown.ChildrenOfType<Dropdown<TestModel?>.DropdownMenu.DrawableDropdownMenuItem>()
.SingleOrDefault(i => i.IsSelected)?
.Item as DropdownMenuItem<TestModel?>)?.Value?.Identifier == "test 2");
}

[Test]
public void TestUserPressAndReleaseOutsideMenu()
{
TestDropdown testDropdown = null!;

AddStep("setup dropdown", () =>
{
testDropdown = createDropdown();
testDropdown.ToggleOnMouseDown = true;
});

toggleDropdownViaPress(() => testDropdown);
assertDropdownIsOpen(() => testDropdown);

AddStep("preselect item 2", () =>
InputManager.MoveMouseTo(testDropdown.Menu.Children[2])
);
AddStep("move outside the menu", () =>
InputManager.MoveMouseTo(InputManager.ScreenSpaceDrawQuad.Centre)
);
AddStep("release mouse buttons", () =>
InputManager.ReleaseButton(MouseButton.Left)
);

assertDropdownIsClosed(() => testDropdown);

AddAssert("item 2 is not selected", () => testDropdown.Current.Value?.Equals(testDropdown.Items.ElementAt(2)) == false);
}

[Test]
public void TestSelectByCurrent()
{
Expand Down Expand Up @@ -709,15 +785,17 @@ public void TestMouseFromTouch()

#endregion

[Test]
public void TestPaddedSearchBar()
[TestCase(false)]
[TestCase(true)]
public void TestPaddedSearchBar(bool toggleOnMouseDown)
{
SearchBarPaddedDropdown dropdown = null!;

AddStep("setup dropdown", () =>
{
Child = dropdown = new SearchBarPaddedDropdown
{
ToggleOnMouseDown = toggleOnMouseDown,
Position = new Vector2(50f, 50f),
Width = 150f,
Items = new TestModel("test").Yield(),
Expand Down Expand Up @@ -788,6 +866,7 @@ private TDropdown[] createDropdowns<TDropdown>(int count)
Position = new Vector2(50f, 50f),
Width = 150,
Items = testItems,
ToggleOnMouseDown = false,
};
}

Expand All @@ -803,12 +882,74 @@ private TDropdown[] createDropdowns<TDropdown>(int count)
return dropdowns;
}

private TDropdown[] createDropdownInScrollableParentScene<TDropdown>()
where TDropdown : TestDropdown, new()
{
var dropdowns = new TDropdown[2];
var testItems = new TestModel[10];
for (int itemIndex = 0; itemIndex < items_to_add; itemIndex++)
testItems[itemIndex] = "test " + itemIndex;

for (int i = 0; i < dropdowns.Length; i++)
{
dropdowns[i] = new TDropdown
{
Width = 150f,
Position = new Vector2(50f, 50f),
Items = testItems,
};
}

var scrollable = createPanel(Color4.DarkRed, new BasicScrollContainer
{
RelativeSizeAxes = Axes.Both,
Child = dropdowns[0],
});

var nonScrollable = createPanel(Color4.DarkBlue, dropdowns[1]);

Child = new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children =
[
scrollable,
nonScrollable,
]
};

return dropdowns;

Container createPanel(Color4 colour, Drawable child)
=> new Container
{
RelativeSizeAxes = Axes.Both,
Width = 0.5f,
Children =
[
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colour,
},
child,
]
};
}

private void toggleDropdownViaClick(Func<TestDropdown> dropdown, string? dropdownName = null) => AddStep($"click {dropdownName ?? "dropdown"}", () =>
{
InputManager.MoveMouseTo(dropdown().Header);
InputManager.Click(MouseButton.Left);
});

private void toggleDropdownViaPress(Func<TestDropdown> dropdown, string? dropdownName = null) => AddStep($"press {dropdownName ?? "dropdown"}", () =>
{
InputManager.MoveMouseTo(dropdown().Header);
InputManager.PressButton(MouseButton.Left);
});

private void assertDropdownIsOpen(Func<TestDropdown> dropdown) => AddAssert("dropdown is open", () => dropdown().Menu.State == MenuState.Open);

private void assertDropdownIsClosed(Func<TestDropdown> dropdown) => AddAssert("dropdown is closed", () => dropdown().Menu.State == MenuState.Closed);
Expand Down
28 changes: 27 additions & 1 deletion osu.Framework/Graphics/UserInterface/Dropdown.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@ public bool AlwaysShowSearchBar
set => Header.AlwaysShowSearchBar = value;
}

/// <summary>
/// Whether this <see cref="Dropdown{T}"/> should open/close on OnMouseDown event.
/// </summary>
public bool ToggleOnMouseDown
{
get => Header.ToggleOnMouseDown;
set => Header.ToggleOnMouseDown = value;
}

public bool AllowNonContiguousMatching
{
get => Menu.AllowNonContiguousMatching;
Expand Down Expand Up @@ -202,7 +211,7 @@ protected virtual LocalisableString GenerateItemText(T item)
/// <summary>
/// Puts the state of this <see cref="Dropdown{T}"/> one level back:
/// - If the dropdown search bar contains text, this method will reset it.
/// - If the dropdown is open, this method wil close it.
/// - If the dropdown is open, this method will close it.
/// </summary>
public bool Back()
{
Expand Down Expand Up @@ -340,6 +349,23 @@ protected override bool OnKeyDown(KeyDownEvent e)
return false;
}

protected override void OnMouseUp(MouseUpEvent e)
{
// Only proceed with the flag
if (!ToggleOnMouseDown)
return;

// Do not close the menu if we are releasing on DropdownHeader
if (Header.IsHovered)
return;

// Cursor is inside the menu and possibly selecting an item
if (Menu.IsHovered)
((IDropdown)this).CommitPreselection();

Menu.Close();
}

private void collectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
Expand Down
Loading
Loading