1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 20:22:55 +08:00

Merge pull request #9478 from peppy/lounge-keyboard-selection

This commit is contained in:
Dean Herbert 2020-07-10 15:16:20 +09:00 committed by GitHub
commit f0dd463416
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 162 additions and 29 deletions

View File

@ -11,6 +11,7 @@ using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Multi.Lounge.Components;
using osuTK.Graphics;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Multiplayer
{
@ -41,12 +42,42 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("first room removed", () => container.Rooms.All(r => r.Room.RoomID.Value != 0));
AddStep("select first room", () => container.Rooms.First().Action?.Invoke());
AddAssert("first room selected", () => Room == RoomManager.Rooms.First());
AddAssert("first room selected", () => checkRoomSelected(RoomManager.Rooms.First()));
AddStep("join first room", () => container.Rooms.First().Action?.Invoke());
AddAssert("first room joined", () => RoomManager.Rooms.First().Status.Value is JoinedRoomStatus);
}
[Test]
public void TestKeyboardNavigation()
{
AddRooms(3);
AddAssert("no selection", () => checkRoomSelected(null));
press(Key.Down);
AddAssert("first room selected", () => checkRoomSelected(RoomManager.Rooms.First()));
press(Key.Up);
AddAssert("first room selected", () => checkRoomSelected(RoomManager.Rooms.First()));
press(Key.Down);
press(Key.Down);
AddAssert("last room selected", () => checkRoomSelected(RoomManager.Rooms.Last()));
press(Key.Enter);
AddAssert("last room joined", () => RoomManager.Rooms.Last().Status.Value is JoinedRoomStatus);
}
private void press(Key down)
{
AddStep($"press {down}", () =>
{
InputManager.PressKey(down);
InputManager.ReleaseKey(down);
});
}
[Test]
public void TestStringFiltering()
{
@ -80,6 +111,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("3 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 3);
}
private bool checkRoomSelected(Room room) => Room == room;
private void joinRequested(Room room) => room.Status.Value = new JoinedRoomStatus();
private class JoinedRoomStatus : RoomStatus

View File

@ -16,54 +16,54 @@ namespace osu.Game.Online.Multiplayer
{
[Cached]
[JsonProperty("id")]
public Bindable<int?> RoomID { get; private set; } = new Bindable<int?>();
public readonly Bindable<int?> RoomID = new Bindable<int?>();
[Cached]
[JsonProperty("name")]
public Bindable<string> Name { get; private set; } = new Bindable<string>();
public readonly Bindable<string> Name = new Bindable<string>();
[Cached]
[JsonProperty("host")]
public Bindable<User> Host { get; private set; } = new Bindable<User>();
public readonly Bindable<User> Host = new Bindable<User>();
[Cached]
[JsonProperty("playlist")]
public BindableList<PlaylistItem> Playlist { get; private set; } = new BindableList<PlaylistItem>();
public readonly BindableList<PlaylistItem> Playlist = new BindableList<PlaylistItem>();
[Cached]
[JsonProperty("channel_id")]
public Bindable<int> ChannelId { get; private set; } = new Bindable<int>();
public readonly Bindable<int> ChannelId = new Bindable<int>();
[Cached]
[JsonIgnore]
public Bindable<TimeSpan> Duration { get; private set; } = new Bindable<TimeSpan>(TimeSpan.FromMinutes(30));
public readonly Bindable<TimeSpan> Duration = new Bindable<TimeSpan>(TimeSpan.FromMinutes(30));
[Cached]
[JsonIgnore]
public Bindable<int?> MaxAttempts { get; private set; } = new Bindable<int?>();
public readonly Bindable<int?> MaxAttempts = new Bindable<int?>();
[Cached]
[JsonIgnore]
public Bindable<RoomStatus> Status { get; private set; } = new Bindable<RoomStatus>(new RoomStatusOpen());
public readonly Bindable<RoomStatus> Status = new Bindable<RoomStatus>(new RoomStatusOpen());
[Cached]
[JsonIgnore]
public Bindable<RoomAvailability> Availability { get; private set; } = new Bindable<RoomAvailability>();
public readonly Bindable<RoomAvailability> Availability = new Bindable<RoomAvailability>();
[Cached]
[JsonIgnore]
public Bindable<GameType> Type { get; private set; } = new Bindable<GameType>(new GameTypeTimeshift());
public readonly Bindable<GameType> Type = new Bindable<GameType>(new GameTypeTimeshift());
[Cached]
[JsonIgnore]
public Bindable<int?> MaxParticipants { get; private set; } = new Bindable<int?>();
public readonly Bindable<int?> MaxParticipants = new Bindable<int?>();
[Cached]
[JsonProperty("recent_participants")]
public BindableList<User> RecentParticipants { get; private set; } = new BindableList<User>();
public readonly BindableList<User> RecentParticipants = new BindableList<User>();
[Cached]
public Bindable<int> ParticipantCount { get; private set; } = new Bindable<int>();
public readonly Bindable<int> ParticipantCount = new Bindable<int>();
// todo: TEMPORARY
[JsonProperty("participant_count")]
@ -83,7 +83,7 @@ namespace osu.Game.Online.Multiplayer
// Only supports retrieval for now
[Cached]
[JsonProperty("ends_at")]
public Bindable<DateTimeOffset> EndDate { get; private set; } = new Bindable<DateTimeOffset>();
public readonly Bindable<DateTimeOffset> EndDate = new Bindable<DateTimeOffset>();
// Todo: Find a better way to do this (https://github.com/ppy/osu-framework/issues/1930)
[JsonProperty("max_attempts", DefaultValueHandling = DefaultValueHandling.Ignore)]
@ -97,7 +97,7 @@ namespace osu.Game.Online.Multiplayer
/// The position of this <see cref="Room"/> in the list. This is not read from or written to the API.
/// </summary>
[JsonIgnore]
public Bindable<int> Position { get; private set; } = new Bindable<int>(-1);
public readonly Bindable<int> Position = new Bindable<int>(-1);
public void CopyFrom(Room other)
{
@ -130,7 +130,7 @@ namespace osu.Game.Online.Multiplayer
RecentParticipants.AddRange(other.RecentParticipants);
}
Position = other.Position;
Position.Value = other.Position.Value;
}
public bool ShouldSerializeRoomID() => false;

View File

@ -136,8 +136,6 @@ namespace osu.Game.Overlays.SearchableList
private class FilterSearchTextBox : SearchTextBox
{
protected override bool AllowCommit => true;
[BackgroundDependencyLoader]
private void load()
{

View File

@ -9,13 +9,17 @@ using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
using osu.Framework.Threading;
using osu.Game.Extensions;
using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings;
using osu.Game.Online.Multiplayer;
using osuTK;
namespace osu.Game.Screens.Multi.Lounge.Components
{
public class RoomsContainer : CompositeDrawable
public class RoomsContainer : CompositeDrawable, IKeyBindingHandler<GlobalAction>
{
public Action<Room> JoinRequested;
@ -88,8 +92,22 @@ namespace osu.Game.Screens.Multi.Lounge.Components
private void addRooms(IEnumerable<Room> rooms)
{
foreach (var r in rooms)
roomFlow.Add(new DrawableRoom(r) { Action = () => selectRoom(r) });
foreach (var room in rooms)
{
roomFlow.Add(new DrawableRoom(room)
{
Action = () =>
{
if (room == selectedRoom.Value)
{
joinSelected();
return;
}
selectRoom(room);
}
});
}
Filter(filter?.Value);
}
@ -115,16 +133,100 @@ namespace osu.Game.Screens.Multi.Lounge.Components
private void selectRoom(Room room)
{
var drawable = roomFlow.FirstOrDefault(r => r.Room == room);
if (drawable != null && drawable.State == SelectionState.Selected)
JoinRequested?.Invoke(room);
else
roomFlow.Children.ForEach(r => r.State = r.Room == room ? SelectionState.Selected : SelectionState.NotSelected);
roomFlow.Children.ForEach(r => r.State = r.Room == room ? SelectionState.Selected : SelectionState.NotSelected);
selectedRoom.Value = room;
}
private void joinSelected()
{
if (selectedRoom.Value == null) return;
JoinRequested?.Invoke(selectedRoom.Value);
}
#region Key selection logic (shared with BeatmapCarousel)
public bool OnPressed(GlobalAction action)
{
switch (action)
{
case GlobalAction.Select:
joinSelected();
return true;
case GlobalAction.SelectNext:
beginRepeatSelection(() => selectNext(1), action);
return true;
case GlobalAction.SelectPrevious:
beginRepeatSelection(() => selectNext(-1), action);
return true;
}
return false;
}
public void OnReleased(GlobalAction action)
{
switch (action)
{
case GlobalAction.SelectNext:
case GlobalAction.SelectPrevious:
endRepeatSelection(action);
break;
}
}
private ScheduledDelegate repeatDelegate;
private object lastRepeatSource;
/// <summary>
/// Begin repeating the specified selection action.
/// </summary>
/// <param name="action">The action to perform.</param>
/// <param name="source">The source of the action. Used in conjunction with <see cref="endRepeatSelection"/> to only cancel the correct action (most recently pressed key).</param>
private void beginRepeatSelection(Action action, object source)
{
endRepeatSelection();
lastRepeatSource = source;
repeatDelegate = this.BeginKeyRepeat(Scheduler, action);
}
private void endRepeatSelection(object source = null)
{
// only the most recent source should be able to cancel the current action.
if (source != null && !EqualityComparer<object>.Default.Equals(lastRepeatSource, source))
return;
repeatDelegate?.Cancel();
repeatDelegate = null;
lastRepeatSource = null;
}
private void selectNext(int direction)
{
var visibleRooms = Rooms.AsEnumerable().Where(r => r.IsPresent);
Room room;
if (selectedRoom.Value == null)
room = visibleRooms.FirstOrDefault()?.Room;
else
{
if (direction < 0)
visibleRooms = visibleRooms.Reverse();
room = visibleRooms.SkipWhile(r => r.Room != selectedRoom.Value).Skip(1).FirstOrDefault()?.Room;
}
// we already have a valid selection only change selection if we still have a room to switch to.
if (room != null)
selectRoom(room);
}
#endregion
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);